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/cli/index.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import { resetGlobalEventBus } from '../agent/event-bus.js'
2
+ import { Session } from '../agent/session.js'
1
3
  import { type Config, loadConfig } from '../config/index.js'
4
+ import { resetTokenUsage } from '../config/tokens.js'
2
5
  import { runInit } from './init.js'
3
6
 
4
7
  export interface CliOptions {
@@ -7,6 +10,10 @@ export interface CliOptions {
7
10
  web?: boolean
8
11
  port?: number
9
12
  init?: boolean
13
+ continueSession?: boolean
14
+ sessionId?: string
15
+ listSessions?: boolean
16
+ deleteSession?: string
10
17
  }
11
18
 
12
19
  export async function parseArgs(argv: string[]): Promise<CliOptions> {
@@ -18,11 +25,69 @@ export async function parseArgs(argv: string[]): Promise<CliOptions> {
18
25
  return { init: true } as CliOptions
19
26
  }
20
27
 
28
+ // Check for new command — clear all saved sessions, then continue fresh
29
+ if (args[0] === 'new') {
30
+ const config = loadConfig()
31
+ Session.clearSavedSession(config.cwd)
32
+ resetTokenUsage(config.cwd)
33
+ resetGlobalEventBus()
34
+ args.splice(0, 1) // Remove 'new', remaining args become normal arguments
35
+ }
36
+
37
+ // ── Session subcommands ──
38
+ if (args[0] === 'session') {
39
+ const sub = args[1]
40
+ if (sub === 'list') {
41
+ const sessions = Session.listSessions()
42
+ if (sessions.length === 0) {
43
+ console.log('No saved sessions found.')
44
+ } else {
45
+ console.log(`\n Saved Sessions (${sessions.length}):`)
46
+ console.log(' ' + '-'.repeat(80))
47
+ console.log(
48
+ ' ID Title Mode Messages Tokens Updated',
49
+ )
50
+ console.log(' ' + '-'.repeat(80))
51
+ for (const s of sessions) {
52
+ const id = s.id.padEnd(9)
53
+ const title = (s.title || '(untitled)').slice(0, 42).padEnd(43)
54
+ const mode = s.mode.padEnd(7)
55
+ const msgs = String(s.messageCount).padEnd(9)
56
+ const tokens = String(s.totalInputTokens + s.totalOutputTokens).padEnd(9)
57
+ const date = s.updatedAt.slice(0, 10)
58
+ console.log(` ${id} ${title} ${mode} ${msgs} ${tokens} ${date}`)
59
+ }
60
+ console.log()
61
+ }
62
+ process.exit(0)
63
+ }
64
+ if (sub === 'delete') {
65
+ const id = args[2]
66
+ if (!id) {
67
+ console.error('Usage: lonny session delete <id>')
68
+ process.exit(1)
69
+ }
70
+ const deleted = Session.deleteSession(id)
71
+ if (deleted) {
72
+ console.log(`Deleted session: ${id}`)
73
+ } else {
74
+ console.error(`Session not found: ${id}`)
75
+ process.exit(1)
76
+ }
77
+ process.exit(0)
78
+ }
79
+ // If just "session" with no subcommand, show usage
80
+ console.log('Usage: lonny session list|delete <id>')
81
+ process.exit(0)
82
+ }
83
+
21
84
  let prompt: string | undefined
22
85
  let autoApprove: boolean | undefined
23
- let mode: 'code' | 'plan' | 'ask' | undefined
86
+ let mode: 'code' | 'plan' | 'ask' | 'loop' | undefined
24
87
  let web = false
25
88
  let port: number | undefined
89
+ let continueSession = false
90
+ let sessionId: string | undefined
26
91
 
27
92
  for (let i = 0; i < args.length; i++) {
28
93
  const arg = args[i]
@@ -31,19 +96,23 @@ export async function parseArgs(argv: string[]): Promise<CliOptions> {
31
96
  } else if (arg === '--auto-approve') {
32
97
  autoApprove = true
33
98
  } else if (arg === '--mode') {
34
- mode = args[++i] as 'code' | 'plan' | 'ask'
99
+ mode = args[++i] as 'code' | 'plan' | 'ask' | 'loop'
35
100
  } else if (arg === '--web') {
36
101
  web = true
37
102
  } else if (arg === '--port') {
38
103
  port = parseInt(args[++i], 10)
104
+ } else if (arg === '--continue' || arg === '-c') {
105
+ continueSession = true
106
+ } else if (arg === '--session' || arg === '-s') {
107
+ sessionId = args[++i]
39
108
  }
40
109
  }
41
110
 
42
- if (!prompt && args.length > 0 && !args[0].startsWith('-')) {
111
+ if (!prompt && args.length > 0 && !args[0].startsWith('-') && args[0] !== 'session') {
43
112
  prompt = args[0]
44
113
  }
45
114
 
46
115
  const config = loadConfig({ autoApprove, mode })
47
116
 
48
- return { config, prompt, web, port }
117
+ return { config, prompt, web, port, continueSession, sessionId }
49
118
  }
@@ -5,7 +5,7 @@ import * as path from 'node:path'
5
5
  export interface Config {
6
6
  apiKey: string
7
7
  baseUrl?: string
8
- mode: 'code' | 'plan' | 'ask'
8
+ mode: 'code' | 'plan' | 'ask' | 'loop'
9
9
  provider: 'openai' | 'anthropic' | 'google' | 'ollama'
10
10
  model: string
11
11
  contextWindow: number
@@ -288,6 +288,12 @@ function loadJsonConfig(): JsonConfig {
288
288
  }
289
289
  }
290
290
 
291
+ function saveJsonConfig(config: JsonConfig): void {
292
+ const configPath = path.join(os.homedir(), '.lonny', 'config.json')
293
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
294
+ cachedJsonConfig = config
295
+ }
296
+
291
297
  /** Check if the model is a DeepSeek model (supports enable_cache). */
292
298
  function isDeepSeekModel(model: string, baseUrl?: string): boolean {
293
299
  if (/deepseek/i.test(model)) return true
@@ -296,7 +302,7 @@ function isDeepSeekModel(model: string, baseUrl?: string): boolean {
296
302
  }
297
303
 
298
304
  export function loadConfig(options?: {
299
- mode?: 'code' | 'plan' | 'ask'
305
+ mode?: 'code' | 'plan' | 'ask' | 'loop'
300
306
  autoApprove?: boolean
301
307
  cwd?: string
302
308
  }): Config {
@@ -304,6 +310,11 @@ export function loadConfig(options?: {
304
310
 
305
311
  const model = jsonConfig.model || 'deepseek-v4-flash'
306
312
  const baseUrl = process.env.LONNY_BASE_URL || jsonConfig.baseUrl || undefined
313
+ const provider = (process.env.LONNY_PROVIDER || jsonConfig.provider || 'openai') as
314
+ | 'openai'
315
+ | 'anthropic'
316
+ | 'google'
317
+ | 'ollama'
307
318
 
308
319
  // Auto-enable cache for DeepSeek models unless explicitly disabled
309
320
  const enableCache = jsonConfig.enableCache ?? (isDeepSeekModel(model, baseUrl) || undefined)
@@ -314,11 +325,7 @@ export function loadConfig(options?: {
314
325
  return {
315
326
  apiKey: jsonConfig.apiKey || '',
316
327
  baseUrl,
317
- provider: (process.env.LONNY_PROVIDER || jsonConfig.provider || 'openai') as
318
- | 'openai'
319
- | 'anthropic'
320
- | 'google'
321
- | 'ollama',
328
+ provider,
322
329
  mode: options?.mode || 'code',
323
330
  model,
324
331
  contextWindow,
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { execSync } from 'node:child_process'
3
3
  import * as os from 'node:os'
4
4
  import { runAgent } from './agent/index.js'
5
+ import { Session } from './agent/session.js'
5
6
  import { parseArgs } from './cli/index.js'
6
7
  import { fmtErr } from './tools/errors.js'
7
8
  import { startTui } from './tui/index.js'
@@ -27,7 +28,9 @@ function tryEnableUtf8(): void {
27
28
 
28
29
  async function main() {
29
30
  tryEnableUtf8()
30
- const { config, prompt, web, port, init } = await parseArgs(process.argv)
31
+ const { config, prompt, web, port, init, continueSession, sessionId } = await parseArgs(
32
+ process.argv,
33
+ )
31
34
 
32
35
  // init command exits after completion
33
36
  if (init) {
@@ -41,12 +44,36 @@ async function main() {
41
44
  process.exit(1)
42
45
  }
43
46
 
47
+ let loadedSession: Session | null = null
48
+
49
+ // Handle --continue and --session flags
50
+ if (continueSession || sessionId) {
51
+ if (sessionId) {
52
+ loadedSession = await Session.loadById(sessionId, config)
53
+ if (loadedSession) {
54
+ console.log(
55
+ `${BLD}Resuming session:${RS} ${loadedSession.sessionTitle || loadedSession.sessionId}`,
56
+ )
57
+ } else {
58
+ console.error(`${RE}Error:${RS} Session not found: ${sessionId}`)
59
+ process.exit(1)
60
+ }
61
+ } else {
62
+ loadedSession = await Session.load(config)
63
+ if (loadedSession) {
64
+ console.log(
65
+ `${BLD}Continuing last session:${RS} ${loadedSession.sessionTitle || loadedSession.sessionId}`,
66
+ )
67
+ }
68
+ }
69
+ }
70
+
44
71
  if (web) {
45
72
  await startWebUi(config, port || 15090)
46
73
  } else if (prompt) {
47
- await runAgent(prompt, config)
74
+ await runAgent(prompt, config, loadedSession ?? undefined)
48
75
  } else {
49
- await startTui(config)
76
+ await startTui(config, loadedSession ?? undefined)
50
77
  }
51
78
  }
52
79
 
@@ -1,5 +1,12 @@
1
+ import * as fs from 'node:fs'
2
+ import * as os from 'node:os'
3
+ import * as path from 'node:path'
1
4
  import { describe, expect, it } from 'vitest'
2
- import { bashTool } from '../bash.js'
5
+ import { bashTool } from '../execute/bash/index.js'
6
+
7
+ function makeTempDir(): string {
8
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'lonny-test-'))
9
+ }
3
10
 
4
11
  describe('bash tool', () => {
5
12
  it('executes a command successfully', async () => {
@@ -17,7 +24,7 @@ describe('bash tool', () => {
17
24
  it('returns error for invalid command', async () => {
18
25
  const result = await bashTool.execute({ command: 'some_nonexistent_command_xyz' })
19
26
  expect(result.success).toBe(false)
20
- expect(result.error).toContain('Command failed')
27
+ expect(result.error).toContain('Command exited with code 1')
21
28
  })
22
29
 
23
30
  it('accepts custom timeout', async () => {
@@ -25,4 +32,183 @@ describe('bash tool', () => {
25
32
  expect(result.success).toBe(true)
26
33
  expect(result.output).toContain('timed')
27
34
  })
35
+
36
+ it('includes description in output when provided', async () => {
37
+ const result = await bashTool.execute({
38
+ command: 'echo hello',
39
+ description: 'testing description',
40
+ })
41
+ expect(result.success).toBe(true)
42
+ expect(result.output).toContain('[bash]')
43
+ expect(result.output).toContain('testing description')
44
+ expect(result.output).toContain('hello')
45
+ })
46
+
47
+ it('runs command in specified cwd', async () => {
48
+ const tmpDir = makeTempDir()
49
+ try {
50
+ // Create a marker file so we can verify cwd
51
+ const result = await bashTool.execute({ command: 'pwd', cwd: tmpDir })
52
+ expect(result.success).toBe(true)
53
+ // On Windows, PowerShell's Get-Location returns the path
54
+ // We just need to check the command executed without error
55
+ if (os.platform() === 'win32') {
56
+ // PowerShell: use Get-Location instead of pwd
57
+ const result2 = await bashTool.execute({
58
+ command: `Get-Location | Select-Object -ExpandProperty Path`,
59
+ cwd: tmpDir,
60
+ })
61
+ expect(result2.success).toBe(true)
62
+ const normalizedOutput = result2.output.replace(/\\/g, '/')
63
+ const normalizedTmp = tmpDir.replace(/\\/g, '/')
64
+ expect(normalizedOutput).toContain(normalizedTmp)
65
+ } else {
66
+ expect(result.output).toContain(tmpDir)
67
+ }
68
+ } finally {
69
+ fs.rmSync(tmpDir, { recursive: true, force: true })
70
+ }
71
+ })
72
+
73
+ it('handles stderr output separately', async () => {
74
+ // PowerShell: write to stderr using Write-Error
75
+ const result = await bashTool.execute({
76
+ command: 'Write-Error "test error" 2>&1 | Out-Null; Write-Output "stdout ok"',
77
+ })
78
+ // 2>&1 redirects stderr to stdout in PowerShell, so it should succeed
79
+ expect(result.success).toBe(true)
80
+ })
81
+
82
+ it('shows (no output) for empty output', async () => {
83
+ // A command that produces no output
84
+ const result = await bashTool.execute({ command: 'Write-Output ""' })
85
+ expect(result.success).toBe(true)
86
+ expect(result.output).toContain('(no output)')
87
+ })
88
+
89
+ it('truncates very large output', async () => {
90
+ // Generate a very large output to trigger truncation
91
+ const result = await bashTool.execute({ command: '1..5000 | ForEach-Object { "Line $_" }' })
92
+ expect(result.success).toBe(true)
93
+ // Check if truncation warning is present (output > 10k chars)
94
+ if (result.output.length > 10000) {
95
+ expect(result.output).toContain('truncated at')
96
+ }
97
+ })
98
+
99
+ it('rejects whitespace-only command', async () => {
100
+ const result = await bashTool.execute({ command: ' ' })
101
+ expect(result.success).toBe(false)
102
+ expect(result.error).toContain('whitespace')
103
+ })
104
+
105
+ it('rejects non-string command type', async () => {
106
+ const result = await bashTool.execute({ command: 123 })
107
+ expect(result.success).toBe(false)
108
+ expect(result.error).toContain('must be a string')
109
+ })
110
+
111
+ it('rejects non-existent cwd', async () => {
112
+ const result = await bashTool.execute({
113
+ command: 'echo test',
114
+ cwd: 'C:\\NonExistent_Directory_XYZ_123',
115
+ })
116
+ expect(result.success).toBe(false)
117
+ expect(result.error).toContain('does not exist')
118
+ })
119
+
120
+ it('blocks destructive rm -rf / command', async () => {
121
+ const result = await bashTool.execute({ command: 'rm -rf /' })
122
+ expect(result.success).toBe(false)
123
+ expect(result.error).toContain('Destructive')
124
+ expect(result.error).toContain('destroy')
125
+ })
126
+
127
+ it('blocks destructive format command', async () => {
128
+ const result = await bashTool.execute({ command: 'format D: /Q' })
129
+ expect(result.success).toBe(false)
130
+ expect(result.error).toContain('Destructive')
131
+ expect(result.error).toContain('destroy')
132
+ })
133
+
134
+ it('clamps timeout to valid range', async () => {
135
+ // Very large timeout should be clamped, not cause error
136
+ const result = await bashTool.execute({ command: 'echo ok', timeout: 999999999 })
137
+ expect(result.success).toBe(true)
138
+ })
139
+
140
+ it('rejects filename-like command (no space before flag)', async () => {
141
+ // This test verifies that the tool handles edge case input gracefully
142
+ const result = await bashTool.execute({ command: '--version' })
143
+ // Should fail with exit code 1, not crash
144
+ expect(result.success).toBe(false)
145
+ })
146
+
147
+ it('blocks Remove-Item -Recurse -Force command', async () => {
148
+ const result = await bashTool.execute({ command: 'Remove-Item C:\\Windows -Recurse -Force' })
149
+ expect(result.success).toBe(false)
150
+ expect(result.error).toContain('Destructive')
151
+ })
152
+
153
+ it('blocks diskpart clean-all command', async () => {
154
+ const result = await bashTool.execute({ command: 'diskpart clean-all' })
155
+ expect(result.success).toBe(false)
156
+ expect(result.error).toContain('Destructive')
157
+ })
158
+
159
+ it('blocks reg delete command', async () => {
160
+ const result = await bashTool.execute({ command: 'reg delete HKLM\\Software' })
161
+ expect(result.success).toBe(false)
162
+ expect(result.error).toContain('Destructive')
163
+ })
164
+
165
+ it('rejects cwd with path traversal', async () => {
166
+ const result = await bashTool.execute({
167
+ command: 'echo test',
168
+ cwd: 'C:\\Users\\..\\Windows',
169
+ })
170
+ expect(result.success).toBe(false)
171
+ expect(result.error).toContain('path traversal')
172
+ expect(result.error).toContain('..')
173
+ })
174
+
175
+ it('rejects cwd with path traversal using forward slashes', async () => {
176
+ const result = await bashTool.execute({
177
+ command: 'echo test',
178
+ cwd: '/Users/../etc',
179
+ })
180
+ expect(result.success).toBe(false)
181
+ expect(result.error).toContain('path traversal')
182
+ })
183
+
184
+ it('redacts sensitive data from output', async () => {
185
+ // Test redaction of API key pattern in output
186
+ const result = await bashTool.execute({
187
+ command: "Write-Output 'api_key=sk-abc123def456ghi789jkl012mno345pqr678stu'",
188
+ })
189
+ expect(result.success).toBe(true)
190
+ // The API key should be redacted
191
+ expect(result.output).not.toContain('sk-abc123def456ghi789jkl012mno345pqr678stu')
192
+ expect(result.output).toContain('[REDACTED]')
193
+ })
194
+
195
+ it('redacts sensitive data from stderr', async () => {
196
+ const result = await bashTool.execute({
197
+ command: "Write-Error 'The token ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx is invalid'",
198
+ })
199
+ // Should not crash; the redacted marker should appear in the output/error
200
+ if (!result.success && result.error) {
201
+ // The token may be split across lines in PowerShell error formatting,
202
+ // but [REDACTED] should appear at least once
203
+ expect(result.error).toContain('[REDACTED]')
204
+ }
205
+ })
206
+
207
+ it('shows platform-specific tip for grep on Windows', async () => {
208
+ if (os.platform() === 'win32') {
209
+ const result = await bashTool.execute({ command: 'grep "pattern" file.txt' })
210
+ expect(result.success).toBe(false)
211
+ expect(result.error).toContain('findstr')
212
+ }
213
+ })
28
214
  })