lonny-agent 0.2.4 → 0.2.7

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 (509) hide show
  1. package/.github/workflows/ci.yml +21 -0
  2. package/AGENTS.md +106 -0
  3. package/CHANGELOG.md +24 -0
  4. package/README.md +326 -326
  5. package/dist/agent/__tests__/session-restore.test.d.ts +2 -0
  6. package/dist/agent/__tests__/session-restore.test.d.ts.map +1 -0
  7. package/dist/agent/__tests__/session-restore.test.js +260 -0
  8. package/dist/agent/__tests__/session-restore.test.js.map +1 -0
  9. package/dist/agent/commands.d.ts +26 -0
  10. package/dist/agent/commands.d.ts.map +1 -0
  11. package/dist/agent/commands.js +253 -0
  12. package/dist/agent/commands.js.map +1 -0
  13. package/dist/agent/compaction.js +8 -1
  14. package/dist/agent/compaction.js.map +1 -1
  15. package/dist/agent/index.d.ts +2 -1
  16. package/dist/agent/index.d.ts.map +1 -1
  17. package/dist/agent/index.js +2 -2
  18. package/dist/agent/index.js.map +1 -1
  19. package/dist/agent/llm.d.ts +4 -0
  20. package/dist/agent/llm.d.ts.map +1 -1
  21. package/dist/agent/memory.d.ts +12 -0
  22. package/dist/agent/memory.d.ts.map +1 -0
  23. package/dist/agent/memory.js +51 -0
  24. package/dist/agent/memory.js.map +1 -0
  25. package/dist/agent/prompt-builder.d.ts +6 -1
  26. package/dist/agent/prompt-builder.d.ts.map +1 -1
  27. package/dist/agent/prompt-builder.js +100 -33
  28. package/dist/agent/prompt-builder.js.map +1 -1
  29. package/dist/agent/providers/google.d.ts.map +1 -1
  30. package/dist/agent/providers/google.js.map +1 -1
  31. package/dist/agent/providers/openai.d.ts +1 -0
  32. package/dist/agent/providers/openai.d.ts.map +1 -1
  33. package/dist/agent/providers/openai.js +59 -6
  34. package/dist/agent/providers/openai.js.map +1 -1
  35. package/dist/agent/session-chat.d.ts +3 -0
  36. package/dist/agent/session-chat.d.ts.map +1 -0
  37. package/dist/agent/session-chat.js +384 -0
  38. package/dist/agent/session-chat.js.map +1 -0
  39. package/dist/agent/session-display.d.ts +29 -0
  40. package/dist/agent/session-display.d.ts.map +1 -0
  41. package/dist/agent/session-display.js +179 -0
  42. package/dist/agent/session-display.js.map +1 -0
  43. package/dist/agent/session-persistence.d.ts +55 -0
  44. package/dist/agent/session-persistence.d.ts.map +1 -0
  45. package/dist/agent/session-persistence.js +101 -0
  46. package/dist/agent/session-persistence.js.map +1 -0
  47. package/dist/agent/session-utils.d.ts +5 -0
  48. package/dist/agent/session-utils.d.ts.map +1 -0
  49. package/dist/agent/session-utils.js +51 -0
  50. package/dist/agent/session-utils.js.map +1 -0
  51. package/dist/agent/session.d.ts +19 -17
  52. package/dist/agent/session.d.ts.map +1 -1
  53. package/dist/agent/session.js +234 -578
  54. package/dist/agent/session.js.map +1 -1
  55. package/dist/cli/index.d.ts +4 -0
  56. package/dist/cli/index.d.ts.map +1 -1
  57. package/dist/cli/index.js +67 -2
  58. package/dist/cli/index.js.map +1 -1
  59. package/dist/config/index.d.ts +2 -2
  60. package/dist/config/index.d.ts.map +1 -1
  61. package/dist/config/index.js +7 -1
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/index.js +24 -3
  64. package/dist/index.js.map +1 -1
  65. package/dist/tools/__tests__/bash.test.js +169 -2
  66. package/dist/tools/__tests__/bash.test.js.map +1 -1
  67. package/dist/tools/__tests__/edit.test.js +1091 -340
  68. package/dist/tools/__tests__/edit.test.js.map +1 -1
  69. package/dist/tools/__tests__/fetch.test.js +1 -1
  70. package/dist/tools/__tests__/fetch.test.js.map +1 -1
  71. package/dist/tools/__tests__/glob.test.js +1 -1
  72. package/dist/tools/__tests__/glob.test.js.map +1 -1
  73. package/dist/tools/__tests__/grep.test.js +33 -1
  74. package/dist/tools/__tests__/grep.test.js.map +1 -1
  75. package/dist/tools/__tests__/ls.test.js +1 -1
  76. package/dist/tools/__tests__/ls.test.js.map +1 -1
  77. package/dist/tools/__tests__/read.test.js +1 -1
  78. package/dist/tools/__tests__/read.test.js.map +1 -1
  79. package/dist/tools/__tests__/registry.test.js +313 -0
  80. package/dist/tools/__tests__/registry.test.js.map +1 -1
  81. package/dist/tools/__tests__/sed.test.d.ts +2 -0
  82. package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
  83. package/dist/tools/__tests__/sed.test.js +228 -0
  84. package/dist/tools/__tests__/sed.test.js.map +1 -0
  85. package/dist/tools/__tests__/write_plan.test.js +1 -1
  86. package/dist/tools/__tests__/write_plan.test.js.map +1 -1
  87. package/dist/tools/bash.d.ts.map +1 -1
  88. package/dist/tools/bash.js +711 -18
  89. package/dist/tools/bash.js.map +1 -1
  90. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts +2 -0
  91. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts.map +1 -0
  92. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js +7 -0
  93. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js.map +1 -0
  94. package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts +6 -0
  95. package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts.map +1 -0
  96. package/dist/tools/codebase/ast/__tests__/ast-edit-final.js +11 -0
  97. package/dist/tools/codebase/ast/__tests__/ast-edit-final.js.map +1 -0
  98. package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts +2 -0
  99. package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts.map +1 -0
  100. package/dist/tools/codebase/ast/__tests__/ast-edit-test.js +9 -0
  101. package/dist/tools/codebase/ast/__tests__/ast-edit-test.js.map +1 -0
  102. package/dist/tools/codebase/ast/__tests__/integration.test.d.ts +2 -0
  103. package/dist/tools/codebase/ast/__tests__/integration.test.d.ts.map +1 -0
  104. package/dist/tools/codebase/ast/__tests__/integration.test.js +415 -0
  105. package/dist/tools/codebase/ast/__tests__/integration.test.js.map +1 -0
  106. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts +2 -0
  107. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts.map +1 -0
  108. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js +414 -0
  109. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js.map +1 -0
  110. package/dist/tools/codebase/ast/adapter.d.ts +23 -0
  111. package/dist/tools/codebase/ast/adapter.d.ts.map +1 -0
  112. package/dist/tools/codebase/ast/adapter.js +11 -0
  113. package/dist/tools/codebase/ast/adapter.js.map +1 -0
  114. package/dist/tools/codebase/ast/tools.d.ts +3 -0
  115. package/dist/tools/codebase/ast/tools.d.ts.map +1 -0
  116. package/dist/tools/codebase/ast/tools.js +286 -0
  117. package/dist/tools/codebase/ast/tools.js.map +1 -0
  118. package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts +3 -0
  119. package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts.map +1 -0
  120. package/dist/tools/codebase/ast/tree-sitter-adapter.js +412 -0
  121. package/dist/tools/codebase/ast/tree-sitter-adapter.js.map +1 -0
  122. package/dist/tools/codebase/ast/tree-sitter-converters.d.ts +4 -0
  123. package/dist/tools/codebase/ast/tree-sitter-converters.d.ts.map +1 -0
  124. package/dist/tools/codebase/ast/tree-sitter-converters.js +447 -0
  125. package/dist/tools/codebase/ast/tree-sitter-converters.js.map +1 -0
  126. package/dist/tools/codebase/ast/types.d.ts +103 -0
  127. package/dist/tools/codebase/ast/types.d.ts.map +1 -0
  128. package/dist/tools/codebase/ast/types.js +2 -0
  129. package/dist/tools/codebase/ast/types.js.map +1 -0
  130. package/dist/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
  131. package/dist/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
  132. package/dist/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
  133. package/dist/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
  134. package/dist/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
  135. package/dist/tools/codebase/find.d.ts +3 -0
  136. package/dist/tools/codebase/find.d.ts.map +1 -0
  137. package/dist/tools/codebase/find.js +61 -0
  138. package/dist/tools/codebase/find.js.map +1 -0
  139. package/dist/tools/codebase/glob.d.ts +3 -0
  140. package/dist/tools/codebase/glob.d.ts.map +1 -0
  141. package/dist/tools/codebase/glob.js +37 -0
  142. package/dist/tools/codebase/glob.js.map +1 -0
  143. package/dist/tools/codebase/grep.d.ts +3 -0
  144. package/dist/tools/codebase/grep.d.ts.map +1 -0
  145. package/dist/tools/codebase/grep.js +169 -0
  146. package/dist/tools/codebase/grep.js.map +1 -0
  147. package/dist/tools/codebase/ls.d.ts +3 -0
  148. package/dist/tools/codebase/ls.d.ts.map +1 -0
  149. package/dist/tools/codebase/ls.js +39 -0
  150. package/dist/tools/codebase/ls.js.map +1 -0
  151. package/dist/tools/codebase/read.d.ts +10 -0
  152. package/dist/tools/codebase/read.d.ts.map +1 -0
  153. package/dist/tools/codebase/read.js +81 -0
  154. package/dist/tools/codebase/read.js.map +1 -0
  155. package/dist/tools/delete_memory.d.ts +3 -0
  156. package/dist/tools/delete_memory.d.ts.map +1 -0
  157. package/dist/tools/delete_memory.js +46 -0
  158. package/dist/tools/delete_memory.js.map +1 -0
  159. package/dist/tools/edit/diff-compute.d.ts +4 -0
  160. package/dist/tools/edit/diff-compute.d.ts.map +1 -0
  161. package/dist/tools/edit/diff-compute.js +18 -0
  162. package/dist/tools/edit/diff-compute.js.map +1 -0
  163. package/dist/tools/edit/diff-render.d.ts +18 -0
  164. package/dist/tools/edit/diff-render.d.ts.map +1 -0
  165. package/dist/tools/edit/diff-render.js +128 -0
  166. package/dist/tools/edit/diff-render.js.map +1 -0
  167. package/dist/tools/edit/edit.d.ts +4 -0
  168. package/dist/tools/edit/edit.d.ts.map +1 -0
  169. package/dist/tools/edit/edit.js +440 -0
  170. package/dist/tools/edit/edit.js.map +1 -0
  171. package/dist/tools/edit/matcher.d.ts +24 -0
  172. package/dist/tools/edit/matcher.d.ts.map +1 -0
  173. package/dist/tools/edit/matcher.js +66 -0
  174. package/dist/tools/edit/matcher.js.map +1 -0
  175. package/dist/tools/edit/parser.d.ts +10 -0
  176. package/dist/tools/edit/parser.d.ts.map +1 -0
  177. package/dist/tools/edit/parser.js +120 -0
  178. package/dist/tools/edit/parser.js.map +1 -0
  179. package/dist/tools/edit/types.d.ts +16 -0
  180. package/dist/tools/edit/types.d.ts.map +1 -0
  181. package/dist/tools/edit/types.js +2 -0
  182. package/dist/tools/edit/types.js.map +1 -0
  183. package/dist/tools/edit/write_plan.d.ts +5 -0
  184. package/dist/tools/edit/write_plan.d.ts.map +1 -0
  185. package/dist/tools/edit/write_plan.js +98 -0
  186. package/dist/tools/edit/write_plan.js.map +1 -0
  187. package/dist/tools/edit.d.ts +50 -9
  188. package/dist/tools/edit.d.ts.map +1 -1
  189. package/dist/tools/edit.js +394 -159
  190. package/dist/tools/edit.js.map +1 -1
  191. package/dist/tools/execute/bash/constants.d.ts +24 -0
  192. package/dist/tools/execute/bash/constants.d.ts.map +1 -0
  193. package/dist/tools/execute/bash/constants.js +88 -0
  194. package/dist/tools/execute/bash/constants.js.map +1 -0
  195. package/dist/tools/execute/bash/errors.d.ts +39 -0
  196. package/dist/tools/execute/bash/errors.d.ts.map +1 -0
  197. package/dist/tools/execute/bash/errors.js +249 -0
  198. package/dist/tools/execute/bash/errors.js.map +1 -0
  199. package/dist/tools/execute/bash/execution.d.ts +18 -0
  200. package/dist/tools/execute/bash/execution.d.ts.map +1 -0
  201. package/dist/tools/execute/bash/execution.js +207 -0
  202. package/dist/tools/execute/bash/execution.js.map +1 -0
  203. package/dist/tools/execute/bash/index.d.ts +3 -0
  204. package/dist/tools/execute/bash/index.d.ts.map +1 -0
  205. package/dist/tools/execute/bash/index.js +117 -0
  206. package/dist/tools/execute/bash/index.js.map +1 -0
  207. package/dist/tools/execute/bash/platform.d.ts +6 -0
  208. package/dist/tools/execute/bash/platform.d.ts.map +1 -0
  209. package/dist/tools/execute/bash/platform.js +64 -0
  210. package/dist/tools/execute/bash/platform.js.map +1 -0
  211. package/dist/tools/execute/bash/security.d.ts +12 -0
  212. package/dist/tools/execute/bash/security.d.ts.map +1 -0
  213. package/dist/tools/execute/bash/security.js +26 -0
  214. package/dist/tools/execute/bash/security.js.map +1 -0
  215. package/dist/tools/execute/bash/validation.d.ts +16 -0
  216. package/dist/tools/execute/bash/validation.d.ts.map +1 -0
  217. package/dist/tools/execute/bash/validation.js +57 -0
  218. package/dist/tools/execute/bash/validation.js.map +1 -0
  219. package/dist/tools/execute/bash.d.ts +3 -0
  220. package/dist/tools/execute/bash.d.ts.map +1 -0
  221. package/dist/tools/execute/bash.js +728 -0
  222. package/dist/tools/execute/bash.js.map +1 -0
  223. package/dist/tools/execute/git.d.ts +3 -0
  224. package/dist/tools/execute/git.d.ts.map +1 -0
  225. package/dist/tools/execute/git.js +183 -0
  226. package/dist/tools/execute/git.js.map +1 -0
  227. package/dist/tools/execute/task_complete.d.ts +7 -0
  228. package/dist/tools/execute/task_complete.d.ts.map +1 -0
  229. package/dist/tools/execute/task_complete.js +38 -0
  230. package/dist/tools/execute/task_complete.js.map +1 -0
  231. package/dist/tools/fetch.d.ts.map +1 -1
  232. package/dist/tools/fetch.js +2 -0
  233. package/dist/tools/fetch.js.map +1 -1
  234. package/dist/tools/find.d.ts.map +1 -1
  235. package/dist/tools/find.js +2 -0
  236. package/dist/tools/find.js.map +1 -1
  237. package/dist/tools/git.d.ts.map +1 -1
  238. package/dist/tools/git.js +2 -0
  239. package/dist/tools/git.js.map +1 -1
  240. package/dist/tools/glob.d.ts.map +1 -1
  241. package/dist/tools/glob.js +2 -0
  242. package/dist/tools/glob.js.map +1 -1
  243. package/dist/tools/grep.d.ts.map +1 -1
  244. package/dist/tools/grep.js +56 -30
  245. package/dist/tools/grep.js.map +1 -1
  246. package/dist/tools/install/clawhub.d.ts +26 -0
  247. package/dist/tools/install/clawhub.d.ts.map +1 -0
  248. package/dist/tools/install/clawhub.js +182 -0
  249. package/dist/tools/install/clawhub.js.map +1 -0
  250. package/dist/tools/install/constants.d.ts +5 -0
  251. package/dist/tools/install/constants.d.ts.map +1 -0
  252. package/dist/tools/install/constants.js +5 -0
  253. package/dist/tools/install/constants.js.map +1 -0
  254. package/dist/tools/install/install_skill.d.ts +3 -0
  255. package/dist/tools/install/install_skill.d.ts.map +1 -0
  256. package/dist/tools/install/install_skill.js +132 -0
  257. package/dist/tools/install/install_skill.js.map +1 -0
  258. package/dist/tools/install/npm.d.ts +8 -0
  259. package/dist/tools/install/npm.d.ts.map +1 -0
  260. package/dist/tools/install/npm.js +52 -0
  261. package/dist/tools/install/npm.js.map +1 -0
  262. package/dist/tools/install/skill-content.d.ts +9 -0
  263. package/dist/tools/install/skill-content.d.ts.map +1 -0
  264. package/dist/tools/install/skill-content.js +89 -0
  265. package/dist/tools/install/skill-content.js.map +1 -0
  266. package/dist/tools/install_skill.d.ts.map +1 -1
  267. package/dist/tools/install_skill.js +2 -0
  268. package/dist/tools/install_skill.js.map +1 -1
  269. package/dist/tools/list_memory.d.ts +3 -0
  270. package/dist/tools/list_memory.d.ts.map +1 -0
  271. package/dist/tools/list_memory.js +55 -0
  272. package/dist/tools/list_memory.js.map +1 -0
  273. package/dist/tools/ls.d.ts.map +1 -1
  274. package/dist/tools/ls.js +2 -0
  275. package/dist/tools/ls.js.map +1 -1
  276. package/dist/tools/memory/delete_memory.d.ts +3 -0
  277. package/dist/tools/memory/delete_memory.d.ts.map +1 -0
  278. package/dist/tools/memory/delete_memory.js +46 -0
  279. package/dist/tools/memory/delete_memory.js.map +1 -0
  280. package/dist/tools/memory/list_memory.d.ts +3 -0
  281. package/dist/tools/memory/list_memory.d.ts.map +1 -0
  282. package/dist/tools/memory/list_memory.js +55 -0
  283. package/dist/tools/memory/list_memory.js.map +1 -0
  284. package/dist/tools/memory/save_memory.d.ts +3 -0
  285. package/dist/tools/memory/save_memory.d.ts.map +1 -0
  286. package/dist/tools/memory/save_memory.js +45 -0
  287. package/dist/tools/memory/save_memory.js.map +1 -0
  288. package/dist/tools/read.d.ts.map +1 -1
  289. package/dist/tools/read.js +2 -0
  290. package/dist/tools/read.js.map +1 -1
  291. package/dist/tools/registry.d.ts +7 -8
  292. package/dist/tools/registry.d.ts.map +1 -1
  293. package/dist/tools/registry.js +114 -46
  294. package/dist/tools/registry.js.map +1 -1
  295. package/dist/tools/save_memory.d.ts +3 -0
  296. package/dist/tools/save_memory.d.ts.map +1 -0
  297. package/dist/tools/save_memory.js +45 -0
  298. package/dist/tools/save_memory.js.map +1 -0
  299. package/dist/tools/search.d.ts.map +1 -1
  300. package/dist/tools/search.js +2 -0
  301. package/dist/tools/search.js.map +1 -1
  302. package/dist/tools/sed.d.ts +4 -0
  303. package/dist/tools/sed.d.ts.map +1 -0
  304. package/dist/tools/sed.js +121 -0
  305. package/dist/tools/sed.js.map +1 -0
  306. package/dist/tools/tree.d.ts +12 -0
  307. package/dist/tools/tree.d.ts.map +1 -0
  308. package/dist/tools/tree.js +87 -0
  309. package/dist/tools/tree.js.map +1 -0
  310. package/dist/tools/types.d.ts +12 -0
  311. package/dist/tools/types.d.ts.map +1 -1
  312. package/dist/tools/web/fetch.d.ts +3 -0
  313. package/dist/tools/web/fetch.d.ts.map +1 -0
  314. package/dist/tools/web/fetch.js +49 -0
  315. package/dist/tools/web/fetch.js.map +1 -0
  316. package/dist/tools/web/search.d.ts +3 -0
  317. package/dist/tools/web/search.d.ts.map +1 -0
  318. package/dist/tools/web/search.js +134 -0
  319. package/dist/tools/web/search.js.map +1 -0
  320. package/dist/tools/write_plan.d.ts.map +1 -1
  321. package/dist/tools/write_plan.js +2 -0
  322. package/dist/tools/write_plan.js.map +1 -1
  323. package/dist/tui/commands.d.ts +4 -0
  324. package/dist/tui/commands.d.ts.map +1 -0
  325. package/dist/tui/commands.js +118 -0
  326. package/dist/tui/commands.js.map +1 -0
  327. package/dist/tui/components/colors.d.ts +36 -0
  328. package/dist/tui/components/colors.d.ts.map +1 -0
  329. package/dist/tui/components/colors.js +48 -0
  330. package/dist/tui/components/colors.js.map +1 -0
  331. package/dist/tui/components/header-bar.d.ts +26 -0
  332. package/dist/tui/components/header-bar.d.ts.map +1 -0
  333. package/dist/tui/components/header-bar.js +78 -0
  334. package/dist/tui/components/header-bar.js.map +1 -0
  335. package/dist/tui/components/index.d.ts +9 -0
  336. package/dist/tui/components/index.d.ts.map +1 -0
  337. package/dist/tui/components/index.js +9 -0
  338. package/dist/tui/components/index.js.map +1 -0
  339. package/dist/tui/components/landing-screen.d.ts +12 -0
  340. package/dist/tui/components/landing-screen.d.ts.map +1 -0
  341. package/dist/tui/components/landing-screen.js +53 -0
  342. package/dist/tui/components/landing-screen.js.map +1 -0
  343. package/dist/tui/components/pixel-logo.d.ts +5 -0
  344. package/dist/tui/components/pixel-logo.d.ts.map +1 -0
  345. package/dist/tui/components/pixel-logo.js +27 -0
  346. package/dist/tui/components/pixel-logo.js.map +1 -0
  347. package/dist/tui/components/plan-utils.d.ts +11 -0
  348. package/dist/tui/components/plan-utils.d.ts.map +1 -0
  349. package/dist/tui/components/plan-utils.js +62 -0
  350. package/dist/tui/components/plan-utils.js.map +1 -0
  351. package/dist/tui/components/plans-list.d.ts +18 -0
  352. package/dist/tui/components/plans-list.d.ts.map +1 -0
  353. package/dist/tui/components/plans-list.js +46 -0
  354. package/dist/tui/components/plans-list.js.map +1 -0
  355. package/dist/tui/components/rich-footer.d.ts +29 -0
  356. package/dist/tui/components/rich-footer.d.ts.map +1 -0
  357. package/dist/tui/components/rich-footer.js +124 -0
  358. package/dist/tui/components/rich-footer.js.map +1 -0
  359. package/dist/tui/components/todo-panel.d.ts +14 -0
  360. package/dist/tui/components/todo-panel.d.ts.map +1 -0
  361. package/dist/tui/components/todo-panel.js +83 -0
  362. package/dist/tui/components/todo-panel.js.map +1 -0
  363. package/dist/tui/components.d.ts.map +1 -1
  364. package/dist/tui/components.js +14 -7
  365. package/dist/tui/components.js.map +1 -1
  366. package/dist/tui/index.d.ts +2 -1
  367. package/dist/tui/index.d.ts.map +1 -1
  368. package/dist/tui/index.js +99 -505
  369. package/dist/tui/index.js.map +1 -1
  370. package/dist/tui/overlays.d.ts +28 -0
  371. package/dist/tui/overlays.d.ts.map +1 -0
  372. package/dist/tui/overlays.js +154 -0
  373. package/dist/tui/overlays.js.map +1 -0
  374. package/dist/tui/themes.d.ts +5 -0
  375. package/dist/tui/themes.d.ts.map +1 -0
  376. package/dist/tui/themes.js +38 -0
  377. package/dist/tui/themes.js.map +1 -0
  378. package/dist/web/index.d.ts.map +1 -1
  379. package/dist/web/index.js +62 -101
  380. package/dist/web/index.js.map +1 -1
  381. package/dist/web/public/app.js +5 -855
  382. package/dist/web/public/confirm.js +70 -0
  383. package/dist/web/public/index.html +121 -120
  384. package/dist/web/public/input.js +107 -0
  385. package/dist/web/public/messages.js +411 -0
  386. package/dist/web/public/sidebar.js +82 -0
  387. package/dist/web/public/state.js +72 -0
  388. package/dist/web/public/style.css +247 -70
  389. package/dist/web/public/utils.js +37 -0
  390. package/dist/web/public/websocket.js +267 -0
  391. package/dist/web/public/ws.js +19 -0
  392. package/dist/web/session-bridge.d.ts.map +1 -1
  393. package/dist/web/session-bridge.js +85 -10
  394. package/dist/web/session-bridge.js.map +1 -1
  395. package/docs/ast-tools.md +117 -0
  396. package/package.json +11 -5
  397. package/scripts/check-line-count.mjs +53 -0
  398. package/scripts/copy-ast-wasm.mjs +32 -0
  399. package/scripts/copy-native.mjs +24 -0
  400. package/scripts/copy-web.mjs +15 -0
  401. package/src/agent/__tests__/session-restore.test.ts +284 -0
  402. package/src/agent/commands.ts +274 -0
  403. package/src/agent/compaction.ts +9 -2
  404. package/src/agent/index.ts +7 -2
  405. package/src/agent/llm.ts +4 -0
  406. package/src/agent/memory.ts +60 -0
  407. package/src/agent/prompt-builder.ts +111 -33
  408. package/src/agent/providers/google.ts +0 -1
  409. package/src/agent/providers/openai.ts +79 -8
  410. package/src/agent/session-chat.ts +480 -0
  411. package/src/agent/session-display.ts +195 -0
  412. package/src/agent/session-persistence.ts +146 -0
  413. package/src/agent/session-utils.ts +54 -0
  414. package/src/agent/session.ts +269 -670
  415. package/src/cli/index.ts +73 -4
  416. package/src/config/index.ts +14 -7
  417. package/src/index.ts +30 -3
  418. package/src/tools/__tests__/bash.test.ts +188 -2
  419. package/src/tools/__tests__/edit.test.ts +1216 -356
  420. package/src/tools/__tests__/fetch.test.ts +1 -1
  421. package/src/tools/__tests__/glob.test.ts +1 -1
  422. package/src/tools/__tests__/grep.test.ts +36 -1
  423. package/src/tools/__tests__/ls.test.ts +1 -1
  424. package/src/tools/__tests__/read.test.ts +1 -1
  425. package/src/tools/__tests__/registry.test.ts +332 -0
  426. package/src/tools/__tests__/write_plan.test.ts +1 -1
  427. package/src/tools/codebase/ast/__tests__/integration.test.ts +452 -0
  428. package/src/tools/codebase/ast/__tests__/tree-sitter.test.ts +466 -0
  429. package/src/tools/codebase/ast/adapter.ts +44 -0
  430. package/src/tools/codebase/ast/tools.ts +322 -0
  431. package/src/tools/codebase/ast/tree-sitter-adapter.ts +468 -0
  432. package/src/tools/codebase/ast/tree-sitter-converters.ts +480 -0
  433. package/src/tools/codebase/ast/types.ts +102 -0
  434. package/src/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
  435. package/src/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
  436. package/src/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
  437. package/src/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
  438. package/src/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
  439. package/src/tools/{find.ts → codebase/find.ts} +4 -2
  440. package/src/tools/{glob.ts → codebase/glob.ts} +4 -2
  441. package/src/tools/{grep.ts → codebase/grep.ts} +61 -32
  442. package/src/tools/{ls.ts → codebase/ls.ts} +4 -2
  443. package/src/tools/{read.ts → codebase/read.ts} +5 -3
  444. package/src/tools/edit/diff-compute.ts +21 -0
  445. package/src/tools/edit/diff-render.ts +145 -0
  446. package/src/tools/edit/edit.ts +468 -0
  447. package/src/tools/edit/matcher.ts +70 -0
  448. package/src/tools/edit/parser.ts +131 -0
  449. package/src/tools/edit/types.ts +19 -0
  450. package/src/tools/{write_plan.ts → edit/write_plan.ts} +4 -2
  451. package/src/tools/execute/bash/constants.ts +93 -0
  452. package/src/tools/execute/bash/errors.ts +304 -0
  453. package/src/tools/execute/bash/execution.ts +211 -0
  454. package/src/tools/execute/bash/index.ts +131 -0
  455. package/src/tools/execute/bash/platform.ts +65 -0
  456. package/src/tools/execute/bash/security.ts +28 -0
  457. package/src/tools/execute/bash/validation.ts +78 -0
  458. package/src/tools/execute/git.ts +209 -0
  459. package/src/tools/execute/task_complete.ts +40 -0
  460. package/src/tools/install/clawhub.ts +222 -0
  461. package/src/tools/install/constants.ts +4 -0
  462. package/src/tools/install/install_skill.ts +150 -0
  463. package/src/tools/install/npm.ts +60 -0
  464. package/src/tools/install/skill-content.ts +108 -0
  465. package/src/tools/memory/delete_memory.ts +46 -0
  466. package/src/tools/memory/list_memory.ts +53 -0
  467. package/src/tools/memory/save_memory.ts +45 -0
  468. package/src/tools/registry.ts +121 -50
  469. package/src/tools/tree.ts +103 -0
  470. package/src/tools/types.ts +13 -0
  471. package/src/tools/{fetch.ts → web/fetch.ts} +4 -2
  472. package/src/tools/{search.ts → web/search.ts} +4 -2
  473. package/src/tui/commands.ts +135 -0
  474. package/src/tui/components/colors.ts +52 -0
  475. package/src/tui/components/header-bar.ts +88 -0
  476. package/src/tui/components/index.ts +8 -0
  477. package/src/tui/components/landing-screen.ts +67 -0
  478. package/src/tui/components/pixel-logo.ts +33 -0
  479. package/src/tui/components/plan-utils.ts +69 -0
  480. package/src/tui/components/plans-list.ts +55 -0
  481. package/src/tui/components/rich-footer.ts +146 -0
  482. package/src/tui/components/todo-panel.ts +95 -0
  483. package/src/tui/index.ts +110 -561
  484. package/src/tui/overlays.ts +203 -0
  485. package/src/tui/themes.ts +41 -0
  486. package/src/web/index.ts +66 -107
  487. package/src/web/public/app.js +5 -855
  488. package/src/web/public/confirm.js +70 -0
  489. package/src/web/public/index.html +121 -120
  490. package/src/web/public/input.js +107 -0
  491. package/src/web/public/messages.js +411 -0
  492. package/src/web/public/sidebar.js +82 -0
  493. package/src/web/public/state.js +72 -0
  494. package/src/web/public/style.css +247 -70
  495. package/src/web/public/utils.js +37 -0
  496. package/src/web/public/websocket.js +267 -0
  497. package/src/web/public/ws.js +19 -0
  498. package/src/web/session-bridge.ts +90 -12
  499. package/.claude/settings.local.json +0 -14
  500. package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
  501. package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
  502. package/.lonny/plan-web-cwd-status.md +0 -38
  503. package/AGENT.md +0 -99
  504. package/src/tools/bash.ts +0 -39
  505. package/src/tools/edit.ts +0 -554
  506. package/src/tools/exec.ts +0 -348
  507. package/src/tools/git.ts +0 -76
  508. package/src/tools/install_skill.ts +0 -539
  509. package/src/tui/components.ts +0 -635
package/src/tui/index.ts CHANGED
@@ -1,49 +1,36 @@
1
- import * as fs from 'node:fs'
2
- import * as path from 'node:path'
3
- import { resetGlobalEventBus } from '../agent/event-bus.js'
4
- import { ensurePromptsDir, loadPromptTemplates } from '../agent/prompt-templates.js'
5
1
  import { formatToolInput, Session, type SessionOutput } from '../agent/session.js'
6
- import { ensureSkillsDir, loadSkills } from '../agent/skills.js'
7
2
  import type { Config } from '../config/index.js'
8
- import { loadTokenUsage, resetTokenUsage } from '../config/tokens.js'
9
- import type {
10
- EditorTheme,
11
- MarkdownTheme,
12
- OverlayHandle,
13
- SelectItem,
14
- SelectListTheme,
15
- SlashCommand,
16
- } from '../pi-tui/index.js'
3
+ import type { OverlayHandle, SlashCommand } from '../pi-tui/index.js'
17
4
  import {
18
5
  Box,
19
6
  CombinedAutocompleteProvider,
20
- Container,
21
7
  Editor,
22
8
  Loader,
23
9
  Markdown,
24
10
  ProcessTerminal,
25
- Text,
26
11
  TUI,
27
12
  } from '../pi-tui/index.js'
28
- import { fmtErr } from '../tools/errors.js'
29
13
  import { fetchDeepSeekBalance, isDeepSeekOfficial } from './balance.js'
14
+ import { handleConfirmInput, sendMessage } from './commands.js'
30
15
  import {
31
16
  colors,
32
17
  LandingScreen,
33
18
  listPlans,
34
- loadTodos,
35
19
  PlansList,
36
- plansToItems,
37
20
  RichFooter,
38
21
  TodoPanel,
39
- } from './components.js'
40
- import { highlightLine } from './highlight.js'
41
-
42
- /**
43
- * Invisible spacer that renders N empty lines.
44
- * Used to reserve space at the bottom of the chat area so the editor
45
- * overlay doesn't cover the last lines of command output.
46
- */
22
+ } from './components/index.js'
23
+ import type { TuiContext } from './overlays.js'
24
+ import {
25
+ refreshPlans,
26
+ showHelpOverlay,
27
+ showPlanDetail,
28
+ showPlansOverlay,
29
+ showTodoPanel,
30
+ updateFooter,
31
+ } from './overlays.js'
32
+ import { editorTheme, markdownTheme, selectTheme } from './themes.js'
33
+
47
34
  class Spacer {
48
35
  constructor(private height: number) {}
49
36
  render(_width: number): string[] {
@@ -52,90 +39,45 @@ class Spacer {
52
39
  invalidate(): void {}
53
40
  }
54
41
 
55
- export async function startTui(config: Config): Promise<void> {
56
- let chatContent = ''
57
- let isRunning = false
58
- let session: Session
59
-
60
- // ── Create markdown theme (OpenCode-style, clean colors, with syntax highlighting) ──
61
- const markdownTheme: MarkdownTheme = {
62
- heading: t => `\x1b[38;2;0;170;255m\x1b[1m${t}\x1b[0m`,
63
- link: t => `\x1b[38;2;0;170;255m\x1b[4m${t}\x1b[0m`,
64
- linkUrl: t => `\x1b[38;2;90;90;90m\x1b[4m${t}\x1b[0m`,
65
- code: t => `\x1b[38;2;255;180;50m${t}\x1b[0m`,
66
- codeBlock: t => `\x1b[38;2;200;200;200m${t}\x1b[0m`,
67
- codeBlockBorder: t => `\x1b[38;2;60;60;60m${t}\x1b[0m`,
68
- highlightCode: (code: string, lang?: string) => {
69
- if (lang && lang.trim()) {
70
- const lines = code.split('\n')
71
- return lines.map(line => highlightLine(line, lang))
72
- }
73
- return code.split('\n')
74
- },
75
- codeBlockIndent: ' ',
76
- quote: t => `\x1b[38;2;130;130;130m${t}\x1b[0m`,
77
- quoteBorder: t => `\x1b[38;2;60;60;60m${t}\x1b[0m`,
78
- hr: t => `\x1b[38;2;60;60;60m${t}\x1b[0m`,
79
- listBullet: t => `\x1b[38;2;130;130;130m${t}\x1b[0m`,
80
- bold: t => `\x1b[1m${t}\x1b[0m`,
81
- italic: t => `\x1b[3m${t}\x1b[0m`,
82
- strikethrough: t => `\x1b[9m${t}\x1b[0m`,
83
- underline: t => `\x1b[4m${t}\x1b[0m`,
84
- }
85
-
86
- // ── Create select list theme ──
87
- const selectTheme: SelectListTheme = {
88
- selectedPrefix: t => `\x1b[38;2;255;255;255m\x1b[48;2;0;128;255m ${t}\x1b[0m`,
89
- selectedText: t => `\x1b[38;2;255;255;255m\x1b[48;2;0;128;255m${t}\x1b[0m`,
90
- description: t => `\x1b[90m${t}\x1b[0m`,
91
- scrollInfo: t => `\x1b[90m${t}\x1b[0m`,
92
- noMatch: t => `\x1b[38;2;255;100;100m${t}\x1b[0m`,
93
- }
94
-
95
- // ── Create editor theme (used by Editor component) ────────────────────
96
- const editorTheme: EditorTheme = {
97
- borderColor: (str: string) => colors.accent(str),
98
- selectList: selectTheme,
99
- }
42
+ export async function startTui(config: Config, preloadedSession?: Session): Promise<void> {
43
+ const ctx = {} as TuiContext
100
44
 
101
45
  // ── Create terminal and TUI ────────────────────────────────────────────
102
46
  const terminal = new ProcessTerminal()
103
- // Patch terminal.write to suppress alternate screen buffer sequences.
104
- // Without this, pi-tui disables the terminal's native scrollback/scrollbar,
105
- // making it impossible to scroll through past conversation history.
106
47
  const origWrite = terminal.write.bind(terminal)
107
48
  terminal.write = (data: string) => {
108
49
  const filtered = data.replace(/\x1b\[\?1049[hl]/g, '')
109
50
  if (filtered) origWrite(filtered)
110
51
  }
111
- // Show hardware cursor by default so IME (Chinese input method) can
112
- // position its candidate window at the correct cursor location.
113
- // The cursor is hidden during agent execution via setShowHardwareCursor(false).
114
- // Note: on some terminals (Windows Terminal), showing the hardware cursor
115
- // can interfere with editor rendering layout. If you see editor border gap,
116
- // set this to false and use PI_HARDWARE_CURSOR=1 env var to enable.
117
52
  const tui = new TUI(terminal, true)
118
53
  tui.setClearOnShrink(true)
119
54
  terminal.setTitle(`lonny ${config.model} ${config.provider}`)
55
+ ctx.tui = tui
120
56
 
121
57
  // ── Create components ──────────────────────────────────────────────────
122
-
123
- // Chat area (full width, no side panel) — created upfront but only added
124
- // to the TUI after the landing screen transitions to chat mode.
125
58
  const chatMarkdown = new Markdown('', 1, 0, markdownTheme)
59
+ ctx.chatMarkdown = chatMarkdown
126
60
  const chatBox = new Box(1, 0)
127
61
  chatBox.addChild(chatMarkdown)
128
- // Reserve space at the bottom so the editor overlay doesn't cover
129
- // the last lines of command output. 13 = maxHeight(12) + offsetY(1).
130
62
  chatBox.addChild(new Spacer(13))
131
63
 
132
- // Chat input — Editor with multi-line support, history, and autocomplete
133
64
  const slashCommands: SlashCommand[] = [
134
- { name: 'mode', description: 'Switch mode (code|plan|ask)', argumentHint: 'code|plan|ask' },
65
+ {
66
+ name: 'mode',
67
+ description: 'Switch mode (code|plan|ask|loop)',
68
+ argumentHint: 'code|plan|ask|loop',
69
+ },
135
70
  { name: 'model', description: 'Switch model', argumentHint: '<name>' },
136
71
  { name: 'plans', description: 'Show plans overlay' },
137
72
  { name: 'prompts', description: 'List prompt templates' },
138
73
  { name: 'skills', description: 'List active skills' },
74
+ { name: 'sessions', description: 'List all saved sessions' },
75
+ {
76
+ name: 'session',
77
+ description: 'Show current session info',
78
+ argumentHint: '[title <name>|delete]',
79
+ },
80
+ { name: 'fork', description: 'Fork a new session from current context' },
139
81
  { name: 'new', description: 'Start a new session' },
140
82
  { name: 'init', description: 'Create .lonny/skills/ & prompts/' },
141
83
  { name: 'help', description: 'Show help' },
@@ -144,106 +86,84 @@ export async function startTui(config: Config): Promise<void> {
144
86
  { name: 'filter', description: 'Filter plans', argumentHint: '<query>' },
145
87
  ]
146
88
  const editor = new Editor(tui, editorTheme)
89
+ ctx.editor = editor
147
90
  editor.setAutocompleteProvider(new CombinedAutocompleteProvider(slashCommands, config.cwd))
148
91
 
149
- // Loader (thinking indicator)
150
92
  const loader = new Loader(tui, colors.running, colors.idle, 'thinking...', { intervalMs: 80 })
93
+ ctx.loader = loader
151
94
 
152
- // Input area — the editor is shown as a bottom-anchored overlay so the
153
- // input always stays at the bottom of the terminal, regardless of how
154
- // much chat content has accumulated.
155
95
  let inputOverlayHandle: OverlayHandle | null = null
156
96
 
157
- // Rich footer (cwd | mode | tokens | model | version + command hints)
158
97
  const footer = new RichFooter(config.cwd, config.model, config.provider)
159
-
160
- // ── Build layout (landing phase) ──
161
- // In the landing phase, only the footer is shown. The chatBox, editor,
162
- // and loader are added after the first message (see landingScreen.onSubmit).
163
- //
164
- // The editor and loader are shown as a bottom-anchored overlay so the
165
- // input area always stays at the bottom of the terminal, regardless of
166
- // how much chat content has accumulated.
167
-
168
- // ── Plan written callback (defined early since it's used by session restore) ──
98
+ ctx.footer = footer
99
+
100
+ // ── Init mutable state ─────────────────────────────────────────────────
101
+ ctx.chatContent = ''
102
+ ctx.isRunning = false
103
+ ctx.pendingConfirmResolve = null
104
+ ctx.plansOverlayHandle = null
105
+ ctx.helpOverlayHandle = null
106
+ ctx.todoPanelHandle = null
107
+ ctx.plansDetailMode = false
108
+ ctx.config = config
109
+
110
+ // ── Plan written callback ──────────────────────────────────────────────
169
111
  const planCb = () => {
170
- refreshPlans()
171
- todoPanel.refresh()
172
- if (plansOverlayHandle?.isHidden() === false) {
173
- showPlansOverlay()
174
- }
175
- }
176
-
177
- // ── Session output ────────────────────────────────────────────────────
178
- // Tool call/result text flows through output.write naturally, interspersed
179
- // with assistant text in the correct order (just like non-TUI mode).
180
- // ── Tool confirmation state ──
181
- let pendingConfirmResolve: ((approved: boolean) => void) | null = null
182
-
183
- function handleConfirmInput(data: string): boolean {
184
- if (!pendingConfirmResolve) return false
185
- const key = data.trim().toLowerCase()
186
- if (key === 'y' || key === 'yes') {
187
- pendingConfirmResolve(true)
188
- pendingConfirmResolve = null
189
- chatContent += 'y\n'
190
- chatMarkdown.setText(chatContent)
191
- tui.requestRender(true)
192
- return true
193
- } else if (key === 'n' || key === 'no' || key === '\r' || key === '') {
194
- pendingConfirmResolve(false)
195
- pendingConfirmResolve = null
196
- chatContent += 'N\n'
197
- chatMarkdown.setText(chatContent)
198
- tui.requestRender(true)
199
- return true
112
+ refreshPlans(ctx)
113
+ ctx.todoPanel.refresh()
114
+ if (ctx.plansOverlayHandle?.isHidden() === false) {
115
+ showPlansOverlay(ctx)
200
116
  }
201
- return false
202
117
  }
118
+ ctx.planCb = planCb
203
119
 
120
+ // ── Session output ─────────────────────────────────────────────────────
204
121
  const output: SessionOutput = {
205
122
  write: (text: string) => {
206
- chatContent += text
207
- chatMarkdown.setText(chatContent)
123
+ ctx.chatContent += text
124
+ ctx.chatMarkdown.setText(ctx.chatContent)
208
125
  },
209
126
  suppressToolOutput: false,
210
127
  confirmTool: async toolCalls => {
211
- chatContent += `\n ${colors.warn('Allow these tool calls?')}\n`
128
+ ctx.chatContent += `\n ${colors.warn('Allow these tool calls?')}\n`
212
129
  for (const tc of toolCalls) {
213
130
  const detail = formatToolInput(tc)
214
- chatContent += ` ${colors.dim('\u2022')} ${colors.accent(tc.name)}${detail ? ` ${colors.dim(detail)}` : ''}\n`
131
+ ctx.chatContent += ` ${colors.dim('\u2022')} ${colors.accent(tc.name)}${detail ? ` ${colors.dim(detail)}` : ''}\n`
215
132
  }
216
- chatContent += ` ${colors.inputPrompt('(y/N)')} `
217
- chatMarkdown.setText(chatContent)
218
- tui.requestRender(true)
133
+ ctx.chatContent += ` ${colors.inputPrompt('(y/N)')} `
134
+ ctx.chatMarkdown.setText(ctx.chatContent)
135
+ ctx.tui.requestRender(true)
219
136
 
220
137
  return new Promise(resolve => {
221
- pendingConfirmResolve = resolve
138
+ ctx.pendingConfirmResolve = resolve
222
139
  })
223
140
  },
224
141
  }
142
+ ctx.output = output
225
143
 
226
- // Try to restore a saved session for this directory (MUST be before landing screen setup)
144
+ // ── Session restore ────────────────────────────────────────────────────
227
145
  let restored = false
228
- const restoredSession = await Session.load(config, output)
146
+ let restoredSession: Session | null = preloadedSession ?? null
147
+ if (!restoredSession) {
148
+ restoredSession = await Session.load(config, output)
149
+ }
229
150
  if (restoredSession) {
230
151
  restored = true
231
- session = restoredSession
232
- session.onPlanWritten = planCb
233
- // Find the last user message from the previous session
234
- const lastUserMsg = [...session.messages].reverse().find(m => m.role === 'user')
152
+ ctx.session = restoredSession
153
+ ctx.session.onPlanWritten = planCb
154
+ const lastUserMsg = [...ctx.session.messages].reverse().find(m => m.role === 'user')
235
155
  const lastQuestion =
236
156
  lastUserMsg && typeof lastUserMsg.content === 'string' ? lastUserMsg.content : null
237
- chatContent = `\n${colors.dim('\u21BA Resumed previous session')}`
157
+ ctx.chatContent = `\n${colors.dim('\u21BA Resumed previous session')}`
238
158
  if (lastQuestion) {
239
159
  const preview = lastQuestion.length > 80 ? `${lastQuestion.slice(0, 80)}\u2026` : lastQuestion
240
- chatContent += ` \u2014 ${colors.userLabel(preview)}`
160
+ ctx.chatContent += ` \u2014 ${colors.userLabel(preview)}`
241
161
  }
242
- chatContent += '\n\n'
243
- chatMarkdown.setText(chatContent)
162
+ ctx.chatContent += '\n\n'
163
+ ctx.chatMarkdown.setText(ctx.chatContent)
244
164
  } else {
245
- session = new Session(config, output)
246
- session.onPlanWritten = planCb
165
+ ctx.session = new Session(config, output)
166
+ ctx.session.onPlanWritten = planCb
247
167
  }
248
168
  // ── Fetch DeepSeek balance at startup (non-blocking)
249
169
  ;(async () => {
@@ -255,8 +175,8 @@ export async function startTui(config: Config): Promise<void> {
255
175
  footer.setWebBalance(balance.webDisplay)
256
176
  tui.requestRender(true)
257
177
  } else if (balance.error) {
258
- chatContent += `\n${colors.warn('\u26A0')} Balance fetch failed: ${balance.error}\n`
259
- chatMarkdown.setText(chatContent)
178
+ ctx.chatContent += `\n${colors.warn('\u26A0')} Balance fetch failed: ${balance.error}\n`
179
+ ctx.chatMarkdown.setText(ctx.chatContent)
260
180
  }
261
181
  }
262
182
  } catch {
@@ -264,10 +184,9 @@ export async function startTui(config: Config): Promise<void> {
264
184
  }
265
185
  })()
266
186
 
267
- // ── Landing screen (centered overlay with pixel logo) ────────────────
187
+ // ── Landing screen ────────────────────────────────────────────────────
268
188
  const landingScreen = new LandingScreen(config.model, config.provider)
269
189
  let landingOverlayHandle: OverlayHandle | null = null
270
- // Only show the landing screen if no session was restored
271
190
  if (!restored) {
272
191
  landingOverlayHandle = tui.showOverlay(landingScreen, {
273
192
  anchor: 'center',
@@ -277,9 +196,7 @@ export async function startTui(config: Config): Promise<void> {
277
196
  tui.setFocus(landingScreen)
278
197
  }
279
198
 
280
- // ── Rich footer bar ──────────────────────────────────
281
- // NOTE: must be added AFTER the landing screen overlay so it renders on
282
- // top and is not covered by the centered overlay.
199
+ // ── Rich footer bar ────────────────────────────────────────────────────
283
200
  const footerWidth = terminal.columns ?? process.stdout.columns ?? 120
284
201
  const footerHandle = tui.showOverlay(footer, {
285
202
  anchor: 'bottom-left',
@@ -289,22 +206,7 @@ export async function startTui(config: Config): Promise<void> {
289
206
 
290
207
  // ── Persistent Todo Side Panel ────────────────────────────────────────
291
208
  const todoPanel = new TodoPanel(config.cwd)
292
- let todoPanelHandle: OverlayHandle | null = null
293
-
294
- function showTodoPanel(): void {
295
- todoPanel.refresh()
296
- const box = new Box(0, 0, colors.bgDark)
297
- box.addChild(todoPanel)
298
- todoPanelHandle = tui.showOverlay(box, {
299
- anchor: 'top-right',
300
- offsetY: 2,
301
- width: 36,
302
- maxHeight: '70%',
303
- offsetX: -1,
304
- nonCapturing: true,
305
- visible: (w: number) => w >= 110,
306
- })
307
- }
209
+ ctx.todoPanel = todoPanel
308
210
 
309
211
  // If a session was restored, immediately transition to chat layout
310
212
  // (skip the landing screen)
@@ -319,370 +221,26 @@ export async function startTui(config: Config): Promise<void> {
319
221
  maxHeight: 12,
320
222
  nonCapturing: false,
321
223
  })
322
- showTodoPanel()
224
+ showTodoPanel(ctx)
323
225
  tui.setFocus(editor)
324
226
  }
325
227
 
326
228
  // ── Plans overlay components ──────────────────────────────────────────
327
229
  const plansList = new PlansList([], 15, selectTheme)
328
- let plansOverlayHandle: OverlayHandle | null = null
329
- let plansDetailMode = false
330
-
331
- function showPlansOverlay(): void {
332
- if (plansOverlayHandle?.isHidden() === false) {
333
- plansOverlayHandle.hide()
334
- plansOverlayHandle = null
335
- plansDetailMode = false
336
- return
337
- }
338
- plansDetailMode = false
339
- const plans = listPlans(config.cwd)
340
- plansList.refresh(plansToItems(plans))
341
-
342
- const headerText = new Text(
343
- ` ${colors.accent('\u25B6')} Plans (${plans.length}) ${colors.dim('Enter=view')}`,
344
- 1,
345
- 0,
346
- colors.headerBg,
347
- )
348
- const container = new Container()
349
- container.addChild(headerText)
350
- if (plans.length > 0) {
351
- container.addChild(plansList)
352
- } else {
353
- container.addChild(new Text(' (no plans yet)', 1, 0, colors.dim))
354
- }
355
-
356
- const box = new Box(1, 1, colors.bgDark)
357
- box.addChild(container)
358
-
359
- plansOverlayHandle = tui.showOverlay(box, {
360
- anchor: 'right-center',
361
- width: 45,
362
- maxHeight: '70%',
363
- offsetX: -1,
364
- })
365
- }
366
-
367
- function showPlanDetail(): void {
368
- if (!plansOverlayHandle || plansOverlayHandle.isHidden()) return
369
- const sel = plansList.getSelectedItem()
370
- if (!sel) return
371
-
372
- plansDetailMode = true
373
- const plans = listPlans(config.cwd)
374
- const plan = plans.find(p => p.name === sel.value)
375
- if (!plan) return
376
-
377
- const todos = loadTodos(plan.fullPath)
378
- const headerText = new Text(
379
- ` ${colors.accent('\u25B6')} ${colors.warn(plan.name)} ${colors.dim('Esc=back')}`,
380
- 1,
381
- 0,
382
- colors.headerBg,
383
- )
384
- const todosText = new Text(`\n ${todos}\n`, 1, 0)
385
- const container = new Container()
386
- container.addChild(headerText)
387
- container.addChild(todosText)
388
-
389
- const box = new Box(1, 1, colors.bgDark)
390
- box.addChild(container)
391
-
392
- // Hide current and show detail
393
- plansOverlayHandle.hide()
394
- plansOverlayHandle = tui.showOverlay(box, {
395
- anchor: 'right-center',
396
- width: 50,
397
- maxHeight: '80%',
398
- offsetX: -1,
399
- })
400
- }
401
-
402
- // ── Help overlay ──────────────────────────────────────────────────────────
403
- let helpOverlayHandle: OverlayHandle | null = null
404
-
405
- function showHelpOverlay(): void {
406
- if (helpOverlayHandle?.isHidden() === false) {
407
- helpOverlayHandle.hide()
408
- helpOverlayHandle = null
409
- return
410
- }
411
- const helpContent =
412
- colors.accent('\u2501').repeat(20) +
413
- '\n' +
414
- ` ${colors.accent('lonny')} ${colors.dim('TUI Help')}\n` +
415
- colors.accent('\u2501').repeat(20) +
416
- '\n\n' +
417
- ` ${colors.dim('Commands:')}\n` +
418
- ` ${colors.inputPrompt('/mode')} code|plan|ask ${colors.dim('Switch mode')}\n` +
419
- ` ${colors.inputPrompt('/model')} <name> ${colors.dim('Switch model')}\n` +
420
- ` ${colors.inputPrompt('/plans')} ${colors.dim('Show plans overlay')}\n` +
421
- ` ${colors.inputPrompt('/new')} ${colors.dim('Start a new session')}\n` +
422
- ` ${colors.inputPrompt('/prompts')} ${colors.dim('List prompt templates')}\n` +
423
- ` ${colors.inputPrompt('/skills')} ${colors.dim('List active skills')}\n` +
424
- ` ${colors.inputPrompt('/init')} ${colors.dim('Create .lonny/skills/ & prompts/')}\n` +
425
- ` ${colors.inputPrompt('/stop')} ${colors.dim('Stop the running agent')}\n` +
426
- ` ${colors.inputPrompt('/exit')} ${colors.dim('Exit')}\n` +
427
- ` ${colors.inputPrompt('/help')} ${colors.dim('This help')}\n\n` +
428
- ` ${colors.dim('Keyboard:')}\n` +
429
- ` ${colors.dim('Enter')} ${colors.dim('Send message')}\n` +
430
- ` ${colors.dim('↑/↓')} ${colors.dim('Navigate history')}\n` +
431
- ` ${colors.dim('Tab')} ${colors.dim('Autocomplete')}\n` +
432
- ` ${colors.dim('?')} ${colors.dim('Toggle this help')}\n\n` +
433
- colors.accent('\u2501').repeat(20)
434
- const helpText = new Text(helpContent, 1, 0)
435
- const helpBox = new Box(1, 1, colors.bgDark)
436
- helpBox.addChild(helpText)
437
- helpOverlayHandle = tui.showOverlay(helpBox, {
438
- anchor: 'center',
439
- width: 46,
440
- maxHeight: 22,
441
- })
442
- }
443
-
444
- // ── Update helpers ──────────────────────────────────────────────────────
445
- function updateFooter(): void {
446
- const plans = listPlans(config.cwd)
447
- footer.setAgentStatus(isRunning ? 'running' : 'idle')
448
- footer.setMode(
449
- session?.config.mode === 'plan' ? 'plan' : session?.config.mode === 'ask' ? 'ask' : 'code',
450
- )
451
- footer.setModel(config.model, config.provider)
452
- const tokenStats = loadTokenUsage(config.cwd)
453
- footer.setTokenUsage(
454
- tokenStats.totalInputTokens,
455
- tokenStats.totalOutputTokens,
456
- tokenStats.totalApiCalls,
457
- )
458
- tui.requestRender(true)
459
- }
460
-
461
- function refreshPlans(): void {
462
- const plans = listPlans(config.cwd)
463
- plansList.refresh(plansToItems(plans))
464
- todoPanel.refresh()
465
- updateFooter()
466
- }
467
-
468
- async function refreshBalance(): Promise<void> {
469
- try {
470
- if (isDeepSeekOfficial(config.baseUrl) && config.apiKey) {
471
- const balance = await fetchDeepSeekBalance(config.apiKey)
472
- if (balance.isAvailable && balance.display) {
473
- footer.setBalance(balance.display)
474
- footer.setWebBalance(balance.webDisplay)
475
- } else {
476
- footer.setBalance('')
477
- footer.setWebBalance('')
478
- }
479
- tui.requestRender(true)
480
- }
481
- } catch {
482
- // Silently ignore
483
- }
484
- }
485
-
486
- // ── Input handling ──────────────────────────────────────────────────────
487
- async function sendMessage(text: string): Promise<void> {
488
- const trimmed = text.trim()
489
- if (!trimmed) return
490
- editor.setText('')
491
- editor.addToHistory(trimmed)
492
-
493
- // Allow slash commands even when agent is running (critical for /stop)
494
- if (trimmed.startsWith('/')) {
495
- const parts = trimmed.slice(1).split(/\s+/)
496
- const cmd = parts[0]
497
- const arg = parts.slice(1).join(' ')
498
-
499
- if (cmd === 'exit' || cmd === 'quit') {
500
- chatContent += `\n${colors.dim('Goodbye!')}\n`
501
- chatMarkdown.setText(chatContent)
502
- tui.stop()
503
- process.exit(0)
504
- return
505
- }
506
-
507
- if (cmd === 'new') {
508
- // If the agent is running, stop the old session gracefully first.
509
- // Without this, the pending chat() promise would continue consuming
510
- // tokens and write stale output into the freshly cleared chat display.
511
- if (isRunning) {
512
- session.stop()
513
- isRunning = false
514
- loader.setMessage('')
515
- tui.setShowHardwareCursor(true)
516
- }
517
- Session.clearSavedSession(config.cwd)
518
- resetTokenUsage(config.cwd)
519
- resetGlobalEventBus()
520
- session = new Session(config, output)
521
- session.onPlanWritten = planCb
522
- chatContent = ''
523
- chatMarkdown.setText('')
524
- plansList.clearFilter()
525
- // Reset editor internal state that setText('') doesn't clear
526
- ;(editor as any).undoStack.clear()
527
- ;(editor as any).history = []
528
- ;(editor as any).killRing.ring = []
529
- updateFooter()
530
- return
531
- }
532
-
533
- if (cmd === 'mode') {
534
- if (arg === 'code' || arg === 'plan' || arg === 'ask') {
535
- await session.setMode(arg)
536
- chatContent += `\n${colors.warn('\u21E8')} Switched to ${arg === 'ask' ? colors.success(arg) : colors.warn(arg)} mode\n`
537
- chatMarkdown.setText(chatContent)
538
- updateFooter()
539
- } else {
540
- chatContent += `\n${colors.error('\u2716')} Usage: ${colors.inputPrompt('/mode code|plan|ask')} (current: ${session.config.mode})\n`
541
- chatMarkdown.setText(chatContent)
542
- }
543
- return
544
- }
545
-
546
- if (cmd === 'model') {
547
- if (arg) {
548
- session.config.model = arg
549
- // Rebuild system prompt with new model context
550
- await session.setMode(session.config.mode) // triggers rebuild
551
- chatContent += `\n${colors.warn('\u21E8')} Model switched to ${colors.warn(arg)}\n`
552
- chatMarkdown.setText(chatContent)
553
- updateFooter()
554
- } else {
555
- chatContent += `\n${colors.inputPrompt('Current model:')} ${colors.dim(session.config.model)}\n`
556
- chatMarkdown.setText(chatContent)
557
- }
558
- return
559
- }
560
-
561
- if (cmd === 'prompts') {
562
- const templates = loadPromptTemplates(config.cwd)
563
- if (templates.length === 0) {
564
- chatContent += `\n${colors.warn('No prompt templates found.')} ${colors.dim('Create .md files in .lonny/prompts/')}\n`
565
- } else {
566
- chatContent += `\n${colors.accent('\u25B6')} ${colors.warn(`Prompt Templates (${templates.length})`)}\n`
567
- for (const t of templates) {
568
- chatContent += ` ${colors.dim('\u2022')} ${colors.inputPrompt(t.name)}`
569
- if (t.description) chatContent += ` ${colors.dim(`\u2014 ${t.description}`)}`
570
- chatContent += '\n'
571
- }
572
- }
573
- chatMarkdown.setText(chatContent)
574
- return
575
- }
576
-
577
- if (cmd === 'skills') {
578
- const skills = loadSkills(config.cwd)
579
- if (skills.length === 0) {
580
- chatContent += `\n${colors.warn('No skills loaded.')} ${colors.dim('Create .md files in .lonny/skills/')}\n`
581
- } else {
582
- chatContent += `\n${colors.accent('\u25B6')} ${colors.warn(`Active Skills (${skills.length})`)}\n`
583
- for (const s of skills) {
584
- chatContent += ` ${colors.dim('\u2022')} ${colors.inputPrompt(s.name)}`
585
- if (s.description) chatContent += ` ${colors.dim(`\u2014 ${s.description}`)}`
586
- chatContent += '\n'
587
- }
588
- }
589
- chatMarkdown.setText(chatContent)
590
- return
591
- }
592
-
593
- if (cmd === 'plans') {
594
- showPlansOverlay()
595
- return
596
- }
597
-
598
- if (cmd === 'filter') {
599
- plansList.setFilter(arg)
600
- tui.requestRender(true)
601
- return
602
- }
603
-
604
- if (cmd === 'help' || cmd === '?') {
605
- showHelpOverlay()
606
- return
607
- }
608
-
609
- if (cmd === 'init') {
610
- ensureSkillsDir(config.cwd)
611
- ensurePromptsDir(config.cwd)
612
- chatContent += `\n${colors.success('\u2714')} Initialized .lonny/skills/ and .lonny/prompts/\n`
613
- chatMarkdown.setText(chatContent)
614
- return
615
- }
616
-
617
- if (cmd === 'stop') {
618
- if (!isRunning) {
619
- chatContent += `\n${colors.dim('Agent is not running.')}\n`
620
- chatMarkdown.setText(chatContent)
621
- return
622
- }
623
- // Tell the session to stop gracefully
624
- session.stop()
625
- chatContent += `\n${colors.warn('\u23F9')} Stopping agent...\n`
626
- chatMarkdown.setText(chatContent)
627
- isRunning = false
628
- loader.stop()
629
- loader.setMessage('')
630
- tui.setShowHardwareCursor(true)
631
- updateFooter()
632
- return
633
- }
634
-
635
- chatContent += `\n${colors.error('\u2716')} Unknown command: /${cmd}. ${colors.dim('Type /help for available commands.')}\n`
636
- chatMarkdown.setText(chatContent)
637
- return
638
- }
639
-
640
- // Block regular messages when agent is already running
641
- if (isRunning) return
642
-
643
- isRunning = true
644
- loader.setMessage('thinking...')
645
- tui.setShowHardwareCursor(false)
646
- updateFooter()
647
-
648
- session
649
- .chat(trimmed)
650
- .then(() => {
651
- isRunning = false
652
- loader.setMessage('')
653
- refreshPlans()
654
- tui.setShowHardwareCursor(true)
655
- updateFooter()
656
- refreshBalance()
657
- })
658
- .catch((err: unknown) => {
659
- isRunning = false
660
- loader.setMessage('')
661
- const errMsg = fmtErr(err)
662
- chatContent += `\n${colors.error('\u2716 Error:')} ${errMsg}\n`
663
- chatMarkdown.setText(chatContent)
664
- tui.setShowHardwareCursor(true)
665
- updateFooter()
666
- })
667
- }
230
+ ctx.plansList = plansList
668
231
 
669
- // Wire up submit on editor (after landing transition)
232
+ // ── Wire up submit on editor ─────────────────────────────────────────-
670
233
  editor.onSubmit = (value: string) => {
671
- sendMessage(value)
234
+ sendMessage(ctx, value)
672
235
  }
673
236
 
674
- // ── Landing screen transition ────────────────────────────────────────────
675
- // When the user presses any key on the landing screen, transition to the
676
- // full chat layout (editor + chat area).
237
+ // ── Landing screen transition ──────────────────────────────────────────
677
238
  landingScreen.onSubmit = () => {
678
- if (isRunning) return
239
+ if (ctx.isRunning) return
679
240
 
680
- // Hide the landing overlay
681
241
  if (landingOverlayHandle) landingOverlayHandle.hide()
682
242
  footer.setPhase('chat')
683
243
 
684
- // Add chat components to the main TUI
685
- // (footer is already an overlay anchored to bottom-left, no need to addChild)
686
244
  tui.addChild(chatBox)
687
245
  tui.addChild(loader)
688
246
  inputOverlayHandle = tui.showOverlay(editor, {
@@ -693,68 +251,62 @@ export async function startTui(config: Config): Promise<void> {
693
251
  nonCapturing: false,
694
252
  })
695
253
 
696
- showTodoPanel()
254
+ showTodoPanel(ctx)
697
255
 
698
- // Focus the chat editor
699
256
  tui.setFocus(editor)
700
257
  tui.requestRender(true)
701
258
  }
702
259
 
703
260
  // ── Input listener ──────────────────────────────────────────────────────
704
261
  tui.addInputListener(data => {
705
- // Check if tool confirmation is pending (consume all input until resolved)
706
- if (pendingConfirmResolve) {
707
- return { consume: handleConfirmInput(data) }
262
+ if (ctx.pendingConfirmResolve) {
263
+ return { consume: handleConfirmInput(ctx, data) }
708
264
  }
709
265
 
710
- // Check if help overlay is active
711
- if (helpOverlayHandle?.isHidden() === false) {
266
+ if (ctx.helpOverlayHandle?.isHidden() === false) {
712
267
  if (data === '\x1b' || data === '\x1b[' || data === '?') {
713
- helpOverlayHandle.hide()
714
- helpOverlayHandle = null
268
+ ctx.helpOverlayHandle.hide()
269
+ ctx.helpOverlayHandle = null
715
270
  }
716
271
  return { consume: true }
717
272
  }
718
273
 
719
- // Check if plans overlay is active
720
- if (plansOverlayHandle?.isHidden() === false) {
274
+ if (ctx.plansOverlayHandle?.isHidden() === false) {
721
275
  if (data === '\x1b' || data === '\x1b[') {
722
- if (plansDetailMode) {
723
- // Go back to plan list
724
- plansOverlayHandle.hide()
725
- plansOverlayHandle = null
726
- plansDetailMode = false
727
- showPlansOverlay()
276
+ if (ctx.plansDetailMode) {
277
+ ctx.plansOverlayHandle.hide()
278
+ ctx.plansOverlayHandle = null
279
+ ctx.plansDetailMode = false
280
+ showPlansOverlay(ctx)
728
281
  } else {
729
- plansOverlayHandle.hide()
730
- plansOverlayHandle = null
282
+ ctx.plansOverlayHandle.hide()
283
+ ctx.plansOverlayHandle = null
731
284
  }
732
285
  return { consume: true }
733
286
  }
734
- if (data === '\r' && !plansDetailMode) {
735
- // Enter: view plan detail
736
- showPlanDetail()
287
+ if (data === '\r' && !ctx.plansDetailMode) {
288
+ showPlanDetail(ctx)
737
289
  return { consume: true }
738
290
  }
739
291
  if (data === '\x1b[A') {
740
292
  const plans = listPlans(config.cwd)
741
293
  if (plans.length > 0) {
742
- const sel = plansList.getSelectedItem()
294
+ const sel = ctx.plansList.getSelectedItem()
743
295
  const idx = sel ? plans.findIndex(p => p.name === sel.value) : -1
744
296
  const nextIdx = idx <= 0 ? plans.length - 1 : idx - 1
745
- plansList.setSelectedIndex(nextIdx)
746
- updateFooter()
297
+ ctx.plansList.setSelectedIndex(nextIdx)
298
+ updateFooter(ctx)
747
299
  }
748
300
  return { consume: true }
749
301
  }
750
302
  if (data === '\x1b[B') {
751
303
  const plans = listPlans(config.cwd)
752
304
  if (plans.length > 0) {
753
- const sel = plansList.getSelectedItem()
305
+ const sel = ctx.plansList.getSelectedItem()
754
306
  const idx = sel ? plans.findIndex(p => p.name === sel.value) : -1
755
307
  const nextIdx = idx === -1 ? 0 : (idx + 1) % plans.length
756
- plansList.setSelectedIndex(nextIdx)
757
- updateFooter()
308
+ ctx.plansList.setSelectedIndex(nextIdx)
309
+ updateFooter(ctx)
758
310
  }
759
311
  return { consume: true }
760
312
  }
@@ -762,7 +314,7 @@ export async function startTui(config: Config): Promise<void> {
762
314
  }
763
315
 
764
316
  if (data === '?') {
765
- showHelpOverlay()
317
+ showHelpOverlay(ctx)
766
318
  return { consume: true }
767
319
  }
768
320
 
@@ -771,16 +323,13 @@ export async function startTui(config: Config): Promise<void> {
771
323
 
772
324
  // ── Initial render ────────────────────────────────────────────────────
773
325
  loader.setMessage('')
774
- refreshPlans()
326
+ refreshPlans(ctx)
775
327
 
776
- // If no session was restored, keep the landing screen and clear chat.
777
- // If a session was restored, chatContent already has the resume message.
778
328
  if (!restored) {
779
- chatMarkdown.setText('')
329
+ ctx.chatMarkdown.setText('')
780
330
  }
781
331
 
782
332
  tui.start()
783
333
 
784
- // Keep alive
785
334
  await new Promise<void>(() => {})
786
335
  }