lonny-agent 0.2.6 → 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 (475) hide show
  1. package/.github/workflows/ci.yml +21 -0
  2. package/AGENTS.md +106 -0
  3. package/CHANGELOG.md +24 -0
  4. package/dist/agent/__tests__/session-restore.test.d.ts +2 -0
  5. package/dist/agent/__tests__/session-restore.test.d.ts.map +1 -0
  6. package/dist/agent/__tests__/session-restore.test.js +260 -0
  7. package/dist/agent/__tests__/session-restore.test.js.map +1 -0
  8. package/dist/agent/commands.d.ts +26 -0
  9. package/dist/agent/commands.d.ts.map +1 -0
  10. package/dist/agent/commands.js +253 -0
  11. package/dist/agent/commands.js.map +1 -0
  12. package/dist/agent/compaction.js +8 -1
  13. package/dist/agent/compaction.js.map +1 -1
  14. package/dist/agent/index.d.ts +2 -1
  15. package/dist/agent/index.d.ts.map +1 -1
  16. package/dist/agent/index.js +2 -2
  17. package/dist/agent/index.js.map +1 -1
  18. package/dist/agent/memory.d.ts +12 -0
  19. package/dist/agent/memory.d.ts.map +1 -0
  20. package/dist/agent/memory.js +51 -0
  21. package/dist/agent/memory.js.map +1 -0
  22. package/dist/agent/prompt-builder.d.ts +6 -1
  23. package/dist/agent/prompt-builder.d.ts.map +1 -1
  24. package/dist/agent/prompt-builder.js +89 -26
  25. package/dist/agent/prompt-builder.js.map +1 -1
  26. package/dist/agent/providers/openai.d.ts +1 -0
  27. package/dist/agent/providers/openai.d.ts.map +1 -1
  28. package/dist/agent/providers/openai.js +16 -3
  29. package/dist/agent/providers/openai.js.map +1 -1
  30. package/dist/agent/session-chat.d.ts +3 -0
  31. package/dist/agent/session-chat.d.ts.map +1 -0
  32. package/dist/agent/session-chat.js +384 -0
  33. package/dist/agent/session-chat.js.map +1 -0
  34. package/dist/agent/session-display.d.ts +29 -0
  35. package/dist/agent/session-display.d.ts.map +1 -0
  36. package/dist/agent/session-display.js +179 -0
  37. package/dist/agent/session-display.js.map +1 -0
  38. package/dist/agent/session-persistence.d.ts +55 -0
  39. package/dist/agent/session-persistence.d.ts.map +1 -0
  40. package/dist/agent/session-persistence.js +101 -0
  41. package/dist/agent/session-persistence.js.map +1 -0
  42. package/dist/agent/session-utils.d.ts +5 -0
  43. package/dist/agent/session-utils.d.ts.map +1 -0
  44. package/dist/agent/session-utils.js +51 -0
  45. package/dist/agent/session-utils.js.map +1 -0
  46. package/dist/agent/session.d.ts +15 -17
  47. package/dist/agent/session.d.ts.map +1 -1
  48. package/dist/agent/session.js +226 -608
  49. package/dist/agent/session.js.map +1 -1
  50. package/dist/cli/index.d.ts +4 -0
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +67 -2
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/config/index.d.ts +2 -2
  55. package/dist/config/index.d.ts.map +1 -1
  56. package/dist/index.js +24 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/tools/__tests__/bash.test.js +168 -1
  59. package/dist/tools/__tests__/bash.test.js.map +1 -1
  60. package/dist/tools/__tests__/edit.test.js +253 -6
  61. package/dist/tools/__tests__/edit.test.js.map +1 -1
  62. package/dist/tools/__tests__/fetch.test.js +1 -1
  63. package/dist/tools/__tests__/fetch.test.js.map +1 -1
  64. package/dist/tools/__tests__/glob.test.js +1 -1
  65. package/dist/tools/__tests__/glob.test.js.map +1 -1
  66. package/dist/tools/__tests__/grep.test.js +1 -1
  67. package/dist/tools/__tests__/grep.test.js.map +1 -1
  68. package/dist/tools/__tests__/ls.test.js +1 -1
  69. package/dist/tools/__tests__/ls.test.js.map +1 -1
  70. package/dist/tools/__tests__/read.test.js +1 -1
  71. package/dist/tools/__tests__/read.test.js.map +1 -1
  72. package/dist/tools/__tests__/registry.test.js +313 -0
  73. package/dist/tools/__tests__/registry.test.js.map +1 -1
  74. package/dist/tools/__tests__/write_plan.test.js +1 -1
  75. package/dist/tools/__tests__/write_plan.test.js.map +1 -1
  76. package/dist/tools/bash.d.ts.map +1 -1
  77. package/dist/tools/bash.js +659 -39
  78. package/dist/tools/bash.js.map +1 -1
  79. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts +2 -0
  80. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts.map +1 -0
  81. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js +7 -0
  82. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js.map +1 -0
  83. package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts +6 -0
  84. package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts.map +1 -0
  85. package/dist/tools/codebase/ast/__tests__/ast-edit-final.js +11 -0
  86. package/dist/tools/codebase/ast/__tests__/ast-edit-final.js.map +1 -0
  87. package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts +2 -0
  88. package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts.map +1 -0
  89. package/dist/tools/codebase/ast/__tests__/ast-edit-test.js +9 -0
  90. package/dist/tools/codebase/ast/__tests__/ast-edit-test.js.map +1 -0
  91. package/dist/tools/codebase/ast/__tests__/integration.test.d.ts +2 -0
  92. package/dist/tools/codebase/ast/__tests__/integration.test.d.ts.map +1 -0
  93. package/dist/tools/codebase/ast/__tests__/integration.test.js +415 -0
  94. package/dist/tools/codebase/ast/__tests__/integration.test.js.map +1 -0
  95. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts +2 -0
  96. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts.map +1 -0
  97. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js +414 -0
  98. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js.map +1 -0
  99. package/dist/tools/codebase/ast/adapter.d.ts +23 -0
  100. package/dist/tools/codebase/ast/adapter.d.ts.map +1 -0
  101. package/dist/tools/codebase/ast/adapter.js +11 -0
  102. package/dist/tools/codebase/ast/adapter.js.map +1 -0
  103. package/dist/tools/codebase/ast/tools.d.ts +3 -0
  104. package/dist/tools/codebase/ast/tools.d.ts.map +1 -0
  105. package/dist/tools/codebase/ast/tools.js +286 -0
  106. package/dist/tools/codebase/ast/tools.js.map +1 -0
  107. package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts +3 -0
  108. package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts.map +1 -0
  109. package/dist/tools/codebase/ast/tree-sitter-adapter.js +412 -0
  110. package/dist/tools/codebase/ast/tree-sitter-adapter.js.map +1 -0
  111. package/dist/tools/codebase/ast/tree-sitter-converters.d.ts +4 -0
  112. package/dist/tools/codebase/ast/tree-sitter-converters.d.ts.map +1 -0
  113. package/dist/tools/codebase/ast/tree-sitter-converters.js +447 -0
  114. package/dist/tools/codebase/ast/tree-sitter-converters.js.map +1 -0
  115. package/dist/tools/codebase/ast/types.d.ts +103 -0
  116. package/dist/tools/codebase/ast/types.d.ts.map +1 -0
  117. package/dist/tools/codebase/ast/types.js +2 -0
  118. package/dist/tools/codebase/ast/types.js.map +1 -0
  119. package/dist/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
  120. package/dist/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
  121. package/dist/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
  122. package/dist/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
  123. package/dist/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
  124. package/dist/tools/codebase/find.d.ts +3 -0
  125. package/dist/tools/codebase/find.d.ts.map +1 -0
  126. package/dist/tools/codebase/find.js +61 -0
  127. package/dist/tools/codebase/find.js.map +1 -0
  128. package/dist/tools/codebase/glob.d.ts +3 -0
  129. package/dist/tools/codebase/glob.d.ts.map +1 -0
  130. package/dist/tools/codebase/glob.js +37 -0
  131. package/dist/tools/codebase/glob.js.map +1 -0
  132. package/dist/tools/codebase/grep.d.ts +3 -0
  133. package/dist/tools/codebase/grep.d.ts.map +1 -0
  134. package/dist/tools/codebase/grep.js +169 -0
  135. package/dist/tools/codebase/grep.js.map +1 -0
  136. package/dist/tools/codebase/ls.d.ts +3 -0
  137. package/dist/tools/codebase/ls.d.ts.map +1 -0
  138. package/dist/tools/codebase/ls.js +39 -0
  139. package/dist/tools/codebase/ls.js.map +1 -0
  140. package/dist/tools/codebase/read.d.ts +10 -0
  141. package/dist/tools/codebase/read.d.ts.map +1 -0
  142. package/dist/tools/codebase/read.js +81 -0
  143. package/dist/tools/codebase/read.js.map +1 -0
  144. package/dist/tools/delete_memory.d.ts +3 -0
  145. package/dist/tools/delete_memory.d.ts.map +1 -0
  146. package/dist/tools/delete_memory.js +46 -0
  147. package/dist/tools/delete_memory.js.map +1 -0
  148. package/dist/tools/edit/diff-compute.d.ts +4 -0
  149. package/dist/tools/edit/diff-compute.d.ts.map +1 -0
  150. package/dist/tools/edit/diff-compute.js +18 -0
  151. package/dist/tools/edit/diff-compute.js.map +1 -0
  152. package/dist/tools/edit/diff-render.d.ts +18 -0
  153. package/dist/tools/edit/diff-render.d.ts.map +1 -0
  154. package/dist/tools/edit/diff-render.js +128 -0
  155. package/dist/tools/edit/diff-render.js.map +1 -0
  156. package/dist/tools/edit/edit.d.ts +4 -0
  157. package/dist/tools/edit/edit.d.ts.map +1 -0
  158. package/dist/tools/edit/edit.js +440 -0
  159. package/dist/tools/edit/edit.js.map +1 -0
  160. package/dist/tools/edit/matcher.d.ts +24 -0
  161. package/dist/tools/edit/matcher.d.ts.map +1 -0
  162. package/dist/tools/edit/matcher.js +66 -0
  163. package/dist/tools/edit/matcher.js.map +1 -0
  164. package/dist/tools/edit/parser.d.ts +10 -0
  165. package/dist/tools/edit/parser.d.ts.map +1 -0
  166. package/dist/tools/edit/parser.js +120 -0
  167. package/dist/tools/edit/parser.js.map +1 -0
  168. package/dist/tools/edit/types.d.ts +16 -0
  169. package/dist/tools/edit/types.d.ts.map +1 -0
  170. package/dist/tools/edit/types.js +2 -0
  171. package/dist/tools/edit/types.js.map +1 -0
  172. package/dist/tools/edit/write_plan.d.ts +5 -0
  173. package/dist/tools/edit/write_plan.d.ts.map +1 -0
  174. package/dist/tools/edit/write_plan.js +98 -0
  175. package/dist/tools/edit/write_plan.js.map +1 -0
  176. package/dist/tools/edit.d.ts.map +1 -1
  177. package/dist/tools/edit.js +66 -1
  178. package/dist/tools/edit.js.map +1 -1
  179. package/dist/tools/execute/bash/constants.d.ts +24 -0
  180. package/dist/tools/execute/bash/constants.d.ts.map +1 -0
  181. package/dist/tools/execute/bash/constants.js +88 -0
  182. package/dist/tools/execute/bash/constants.js.map +1 -0
  183. package/dist/tools/execute/bash/errors.d.ts +39 -0
  184. package/dist/tools/execute/bash/errors.d.ts.map +1 -0
  185. package/dist/tools/execute/bash/errors.js +249 -0
  186. package/dist/tools/execute/bash/errors.js.map +1 -0
  187. package/dist/tools/execute/bash/execution.d.ts +18 -0
  188. package/dist/tools/execute/bash/execution.d.ts.map +1 -0
  189. package/dist/tools/execute/bash/execution.js +207 -0
  190. package/dist/tools/execute/bash/execution.js.map +1 -0
  191. package/dist/tools/execute/bash/index.d.ts +3 -0
  192. package/dist/tools/execute/bash/index.d.ts.map +1 -0
  193. package/dist/tools/execute/bash/index.js +117 -0
  194. package/dist/tools/execute/bash/index.js.map +1 -0
  195. package/dist/tools/execute/bash/platform.d.ts +6 -0
  196. package/dist/tools/execute/bash/platform.d.ts.map +1 -0
  197. package/dist/tools/execute/bash/platform.js +64 -0
  198. package/dist/tools/execute/bash/platform.js.map +1 -0
  199. package/dist/tools/execute/bash/security.d.ts +12 -0
  200. package/dist/tools/execute/bash/security.d.ts.map +1 -0
  201. package/dist/tools/execute/bash/security.js +26 -0
  202. package/dist/tools/execute/bash/security.js.map +1 -0
  203. package/dist/tools/execute/bash/validation.d.ts +16 -0
  204. package/dist/tools/execute/bash/validation.d.ts.map +1 -0
  205. package/dist/tools/execute/bash/validation.js +57 -0
  206. package/dist/tools/execute/bash/validation.js.map +1 -0
  207. package/dist/tools/execute/bash.d.ts +3 -0
  208. package/dist/tools/execute/bash.d.ts.map +1 -0
  209. package/dist/tools/execute/bash.js +728 -0
  210. package/dist/tools/execute/bash.js.map +1 -0
  211. package/dist/tools/execute/git.d.ts +3 -0
  212. package/dist/tools/execute/git.d.ts.map +1 -0
  213. package/dist/tools/execute/git.js +183 -0
  214. package/dist/tools/execute/git.js.map +1 -0
  215. package/dist/tools/execute/task_complete.d.ts +7 -0
  216. package/dist/tools/execute/task_complete.d.ts.map +1 -0
  217. package/dist/tools/execute/task_complete.js +38 -0
  218. package/dist/tools/execute/task_complete.js.map +1 -0
  219. package/dist/tools/fetch.d.ts.map +1 -1
  220. package/dist/tools/fetch.js +2 -0
  221. package/dist/tools/fetch.js.map +1 -1
  222. package/dist/tools/find.d.ts.map +1 -1
  223. package/dist/tools/find.js +2 -0
  224. package/dist/tools/find.js.map +1 -1
  225. package/dist/tools/git.d.ts.map +1 -1
  226. package/dist/tools/git.js +2 -0
  227. package/dist/tools/git.js.map +1 -1
  228. package/dist/tools/glob.d.ts.map +1 -1
  229. package/dist/tools/glob.js +2 -0
  230. package/dist/tools/glob.js.map +1 -1
  231. package/dist/tools/grep.d.ts.map +1 -1
  232. package/dist/tools/grep.js +2 -0
  233. package/dist/tools/grep.js.map +1 -1
  234. package/dist/tools/install/clawhub.d.ts +26 -0
  235. package/dist/tools/install/clawhub.d.ts.map +1 -0
  236. package/dist/tools/install/clawhub.js +182 -0
  237. package/dist/tools/install/clawhub.js.map +1 -0
  238. package/dist/tools/install/constants.d.ts +5 -0
  239. package/dist/tools/install/constants.d.ts.map +1 -0
  240. package/dist/tools/install/constants.js +5 -0
  241. package/dist/tools/install/constants.js.map +1 -0
  242. package/dist/tools/install/install_skill.d.ts +3 -0
  243. package/dist/tools/install/install_skill.d.ts.map +1 -0
  244. package/dist/tools/install/install_skill.js +132 -0
  245. package/dist/tools/install/install_skill.js.map +1 -0
  246. package/dist/tools/install/npm.d.ts +8 -0
  247. package/dist/tools/install/npm.d.ts.map +1 -0
  248. package/dist/tools/install/npm.js +52 -0
  249. package/dist/tools/install/npm.js.map +1 -0
  250. package/dist/tools/install/skill-content.d.ts +9 -0
  251. package/dist/tools/install/skill-content.d.ts.map +1 -0
  252. package/dist/tools/install/skill-content.js +89 -0
  253. package/dist/tools/install/skill-content.js.map +1 -0
  254. package/dist/tools/install_skill.d.ts.map +1 -1
  255. package/dist/tools/install_skill.js +2 -0
  256. package/dist/tools/install_skill.js.map +1 -1
  257. package/dist/tools/list_memory.d.ts +3 -0
  258. package/dist/tools/list_memory.d.ts.map +1 -0
  259. package/dist/tools/list_memory.js +55 -0
  260. package/dist/tools/list_memory.js.map +1 -0
  261. package/dist/tools/ls.d.ts.map +1 -1
  262. package/dist/tools/ls.js +2 -0
  263. package/dist/tools/ls.js.map +1 -1
  264. package/dist/tools/memory/delete_memory.d.ts +3 -0
  265. package/dist/tools/memory/delete_memory.d.ts.map +1 -0
  266. package/dist/tools/memory/delete_memory.js +46 -0
  267. package/dist/tools/memory/delete_memory.js.map +1 -0
  268. package/dist/tools/memory/list_memory.d.ts +3 -0
  269. package/dist/tools/memory/list_memory.d.ts.map +1 -0
  270. package/dist/tools/memory/list_memory.js +55 -0
  271. package/dist/tools/memory/list_memory.js.map +1 -0
  272. package/dist/tools/memory/save_memory.d.ts +3 -0
  273. package/dist/tools/memory/save_memory.d.ts.map +1 -0
  274. package/dist/tools/memory/save_memory.js +45 -0
  275. package/dist/tools/memory/save_memory.js.map +1 -0
  276. package/dist/tools/read.d.ts.map +1 -1
  277. package/dist/tools/read.js +2 -0
  278. package/dist/tools/read.js.map +1 -1
  279. package/dist/tools/registry.d.ts +7 -2
  280. package/dist/tools/registry.d.ts.map +1 -1
  281. package/dist/tools/registry.js +112 -17
  282. package/dist/tools/registry.js.map +1 -1
  283. package/dist/tools/save_memory.d.ts +3 -0
  284. package/dist/tools/save_memory.d.ts.map +1 -0
  285. package/dist/tools/save_memory.js +45 -0
  286. package/dist/tools/save_memory.js.map +1 -0
  287. package/dist/tools/search.d.ts.map +1 -1
  288. package/dist/tools/search.js +2 -0
  289. package/dist/tools/search.js.map +1 -1
  290. package/dist/tools/tree.d.ts +12 -0
  291. package/dist/tools/tree.d.ts.map +1 -0
  292. package/dist/tools/tree.js +87 -0
  293. package/dist/tools/tree.js.map +1 -0
  294. package/dist/tools/types.d.ts +12 -0
  295. package/dist/tools/types.d.ts.map +1 -1
  296. package/dist/tools/web/fetch.d.ts +3 -0
  297. package/dist/tools/web/fetch.d.ts.map +1 -0
  298. package/dist/tools/web/fetch.js +49 -0
  299. package/dist/tools/web/fetch.js.map +1 -0
  300. package/dist/tools/web/search.d.ts +3 -0
  301. package/dist/tools/web/search.d.ts.map +1 -0
  302. package/dist/tools/web/search.js +134 -0
  303. package/dist/tools/web/search.js.map +1 -0
  304. package/dist/tools/write_plan.d.ts.map +1 -1
  305. package/dist/tools/write_plan.js +2 -0
  306. package/dist/tools/write_plan.js.map +1 -1
  307. package/dist/tui/commands.d.ts +4 -0
  308. package/dist/tui/commands.d.ts.map +1 -0
  309. package/dist/tui/commands.js +118 -0
  310. package/dist/tui/commands.js.map +1 -0
  311. package/dist/tui/components/colors.d.ts +36 -0
  312. package/dist/tui/components/colors.d.ts.map +1 -0
  313. package/dist/tui/components/colors.js +48 -0
  314. package/dist/tui/components/colors.js.map +1 -0
  315. package/dist/tui/components/header-bar.d.ts +26 -0
  316. package/dist/tui/components/header-bar.d.ts.map +1 -0
  317. package/dist/tui/components/header-bar.js +78 -0
  318. package/dist/tui/components/header-bar.js.map +1 -0
  319. package/dist/tui/components/index.d.ts +9 -0
  320. package/dist/tui/components/index.d.ts.map +1 -0
  321. package/dist/tui/components/index.js +9 -0
  322. package/dist/tui/components/index.js.map +1 -0
  323. package/dist/tui/components/landing-screen.d.ts +12 -0
  324. package/dist/tui/components/landing-screen.d.ts.map +1 -0
  325. package/dist/tui/components/landing-screen.js +53 -0
  326. package/dist/tui/components/landing-screen.js.map +1 -0
  327. package/dist/tui/components/pixel-logo.d.ts +5 -0
  328. package/dist/tui/components/pixel-logo.d.ts.map +1 -0
  329. package/dist/tui/components/pixel-logo.js +27 -0
  330. package/dist/tui/components/pixel-logo.js.map +1 -0
  331. package/dist/tui/components/plan-utils.d.ts +11 -0
  332. package/dist/tui/components/plan-utils.d.ts.map +1 -0
  333. package/dist/tui/components/plan-utils.js +62 -0
  334. package/dist/tui/components/plan-utils.js.map +1 -0
  335. package/dist/tui/components/plans-list.d.ts +18 -0
  336. package/dist/tui/components/plans-list.d.ts.map +1 -0
  337. package/dist/tui/components/plans-list.js +46 -0
  338. package/dist/tui/components/plans-list.js.map +1 -0
  339. package/dist/tui/components/rich-footer.d.ts +29 -0
  340. package/dist/tui/components/rich-footer.d.ts.map +1 -0
  341. package/dist/tui/components/rich-footer.js +124 -0
  342. package/dist/tui/components/rich-footer.js.map +1 -0
  343. package/dist/tui/components/todo-panel.d.ts +14 -0
  344. package/dist/tui/components/todo-panel.d.ts.map +1 -0
  345. package/dist/tui/components/todo-panel.js +83 -0
  346. package/dist/tui/components/todo-panel.js.map +1 -0
  347. package/dist/tui/components.d.ts.map +1 -1
  348. package/dist/tui/components.js +14 -7
  349. package/dist/tui/components.js.map +1 -1
  350. package/dist/tui/index.d.ts +2 -1
  351. package/dist/tui/index.d.ts.map +1 -1
  352. package/dist/tui/index.js +99 -505
  353. package/dist/tui/index.js.map +1 -1
  354. package/dist/tui/overlays.d.ts +28 -0
  355. package/dist/tui/overlays.d.ts.map +1 -0
  356. package/dist/tui/overlays.js +154 -0
  357. package/dist/tui/overlays.js.map +1 -0
  358. package/dist/tui/themes.d.ts +5 -0
  359. package/dist/tui/themes.d.ts.map +1 -0
  360. package/dist/tui/themes.js +38 -0
  361. package/dist/tui/themes.js.map +1 -0
  362. package/dist/web/index.d.ts.map +1 -1
  363. package/dist/web/index.js +60 -122
  364. package/dist/web/index.js.map +1 -1
  365. package/dist/web/public/index.html +1 -1
  366. package/dist/web/public/messages.js +41 -25
  367. package/dist/web/public/state.js +72 -72
  368. package/dist/web/public/style.css +1126 -996
  369. package/dist/web/public/websocket.js +267 -267
  370. package/dist/web/session-bridge.d.ts.map +1 -1
  371. package/dist/web/session-bridge.js +82 -3
  372. package/dist/web/session-bridge.js.map +1 -1
  373. package/docs/ast-tools.md +117 -0
  374. package/package.json +60 -55
  375. package/scripts/check-line-count.mjs +53 -0
  376. package/scripts/copy-ast-wasm.mjs +32 -0
  377. package/src/agent/__tests__/session-restore.test.ts +284 -0
  378. package/src/agent/commands.ts +274 -0
  379. package/src/agent/compaction.ts +9 -2
  380. package/src/agent/index.ts +30 -25
  381. package/src/agent/memory.ts +60 -0
  382. package/src/agent/prompt-builder.ts +100 -26
  383. package/src/agent/providers/anthropic.ts +179 -179
  384. package/src/agent/providers/google.ts +210 -210
  385. package/src/agent/providers/ollama.ts +186 -186
  386. package/src/agent/providers/openai.ts +18 -3
  387. package/src/agent/session-chat.ts +480 -0
  388. package/src/agent/session-display.ts +195 -0
  389. package/src/agent/session-persistence.ts +146 -0
  390. package/src/agent/session-utils.ts +54 -0
  391. package/src/agent/session.ts +261 -718
  392. package/src/cli/index.ts +73 -4
  393. package/src/config/index.ts +342 -342
  394. package/src/index.ts +30 -3
  395. package/src/tools/__tests__/bash.test.ts +187 -1
  396. package/src/tools/__tests__/edit.test.ts +275 -11
  397. package/src/tools/__tests__/fetch.test.ts +1 -1
  398. package/src/tools/__tests__/glob.test.ts +1 -1
  399. package/src/tools/__tests__/grep.test.ts +1 -1
  400. package/src/tools/__tests__/ls.test.ts +1 -1
  401. package/src/tools/__tests__/read.test.ts +1 -1
  402. package/src/tools/__tests__/registry.test.ts +332 -0
  403. package/src/tools/__tests__/write_plan.test.ts +1 -1
  404. package/src/tools/codebase/ast/__tests__/integration.test.ts +452 -0
  405. package/src/tools/codebase/ast/__tests__/tree-sitter.test.ts +466 -0
  406. package/src/tools/codebase/ast/adapter.ts +44 -0
  407. package/src/tools/codebase/ast/tools.ts +322 -0
  408. package/src/tools/codebase/ast/tree-sitter-adapter.ts +468 -0
  409. package/src/tools/codebase/ast/tree-sitter-converters.ts +480 -0
  410. package/src/tools/codebase/ast/types.ts +102 -0
  411. package/src/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
  412. package/src/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
  413. package/src/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
  414. package/src/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
  415. package/src/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
  416. package/src/tools/{find.ts → codebase/find.ts} +4 -2
  417. package/src/tools/{glob.ts → codebase/glob.ts} +4 -2
  418. package/src/tools/{grep.ts → codebase/grep.ts} +4 -2
  419. package/src/tools/{ls.ts → codebase/ls.ts} +4 -2
  420. package/src/tools/{read.ts → codebase/read.ts} +5 -3
  421. package/src/tools/edit/diff-compute.ts +21 -0
  422. package/src/tools/edit/diff-render.ts +145 -0
  423. package/src/tools/edit/edit.ts +468 -0
  424. package/src/tools/edit/matcher.ts +70 -0
  425. package/src/tools/edit/parser.ts +131 -0
  426. package/src/tools/edit/types.ts +19 -0
  427. package/src/tools/{write_plan.ts → edit/write_plan.ts} +4 -2
  428. package/src/tools/execute/bash/constants.ts +93 -0
  429. package/src/tools/execute/bash/errors.ts +304 -0
  430. package/src/tools/execute/bash/execution.ts +211 -0
  431. package/src/tools/execute/bash/index.ts +131 -0
  432. package/src/tools/execute/bash/platform.ts +65 -0
  433. package/src/tools/execute/bash/security.ts +28 -0
  434. package/src/tools/execute/bash/validation.ts +78 -0
  435. package/src/tools/execute/git.ts +209 -0
  436. package/src/tools/execute/task_complete.ts +40 -0
  437. package/src/tools/install/clawhub.ts +222 -0
  438. package/src/tools/install/constants.ts +4 -0
  439. package/src/tools/install/install_skill.ts +150 -0
  440. package/src/tools/install/npm.ts +60 -0
  441. package/src/tools/install/skill-content.ts +108 -0
  442. package/src/tools/memory/delete_memory.ts +46 -0
  443. package/src/tools/memory/list_memory.ts +53 -0
  444. package/src/tools/memory/save_memory.ts +45 -0
  445. package/src/tools/registry.ts +119 -19
  446. package/src/tools/tree.ts +103 -0
  447. package/src/tools/types.ts +13 -0
  448. package/src/tools/{fetch.ts → web/fetch.ts} +4 -2
  449. package/src/tools/{search.ts → web/search.ts} +4 -2
  450. package/src/tui/commands.ts +135 -0
  451. package/src/tui/components/colors.ts +52 -0
  452. package/src/tui/components/header-bar.ts +88 -0
  453. package/src/tui/components/index.ts +8 -0
  454. package/src/tui/components/landing-screen.ts +67 -0
  455. package/src/tui/components/pixel-logo.ts +33 -0
  456. package/src/tui/components/plan-utils.ts +69 -0
  457. package/src/tui/components/plans-list.ts +55 -0
  458. package/src/tui/components/rich-footer.ts +146 -0
  459. package/src/tui/components/todo-panel.ts +95 -0
  460. package/src/tui/index.ts +335 -786
  461. package/src/tui/overlays.ts +203 -0
  462. package/src/tui/themes.ts +41 -0
  463. package/src/web/index.ts +64 -127
  464. package/src/web/public/index.html +1 -1
  465. package/src/web/public/messages.js +41 -25
  466. package/src/web/public/state.js +72 -72
  467. package/src/web/public/style.css +1126 -996
  468. package/src/web/public/websocket.js +267 -267
  469. package/src/web/session-bridge.ts +272 -193
  470. package/AGENT.md +0 -99
  471. package/src/tools/bash.ts +0 -125
  472. package/src/tools/edit.ts +0 -747
  473. package/src/tools/git.ts +0 -76
  474. package/src/tools/install_skill.ts +0 -539
  475. package/src/tui/components.ts +0 -635
@@ -0,0 +1,209 @@
1
+ import { exec } from 'node:child_process'
2
+ import * as fs from 'node:fs'
3
+ import * as path from 'node:path'
4
+ import type { Tool, ToolDefinition } from '../types.js'
5
+
6
+ /** Normalize a git command string — strip common LLM mistakes. */
7
+ function normalizeGitCommand(raw: string): string {
8
+ let cmd = String(raw).trim()
9
+ // LLM often writes `git("git status")` or `git("git status --short")`
10
+ cmd = cmd.replace(/^git\s+/, '')
11
+ // Strip leading `git(` or `git'` artifacts from LLM hallucination
12
+ cmd = cmd.replace(/^git['"(]\s*/, '').replace(/['")]\s*$/, '')
13
+ return cmd
14
+ }
15
+
16
+ /** Common corrections map for git subcommand typos. */
17
+ const COMMON_TYPOS: Record<string, string> = {
18
+ stauts: 'status',
19
+ staus: 'status',
20
+ statsu: 'status',
21
+ comit: 'commit',
22
+ commmit: 'commit',
23
+ brach: 'branch',
24
+ branc: 'branch',
25
+ chekout: 'checkout',
26
+ checkut: 'checkout',
27
+ 'diff --staged': 'diff --cached',
28
+ 'diff --stage': 'diff --cached',
29
+ }
30
+
31
+ /** Suggest a fix for a failed git command based on stderr content. */
32
+ function suggestFix(cmd: string, stderr: string): string | null {
33
+ const lower = stderr.toLowerCase()
34
+ if (lower.includes('not a git repository') || lower.includes('not a git repo')) {
35
+ return 'Not a git repository. Check that you are in the correct directory.'
36
+ }
37
+ if (lower.includes('did not match any file(s) known to git')) {
38
+ return 'The path does not match any tracked files. Use `git status` to see tracked files first.'
39
+ }
40
+ if (lower.includes('pathspec') && lower.includes('did not match')) {
41
+ return 'The pathspec did not match. Check the file path is correct and tracked by git.'
42
+ }
43
+ if (lower.includes('unknown option') || lower.includes('usage:')) {
44
+ return 'Unknown flag or syntax error. Check `git help` for the correct usage.'
45
+ }
46
+ if (lower.includes('ambiguous argument')) {
47
+ return 'Ambiguous argument. Try quoting the path or using `--` to separate options from paths.'
48
+ }
49
+ if (lower.includes('permission denied') || lower.includes('denied')) {
50
+ return 'Permission denied. You may not have access to this repository.'
51
+ }
52
+ return null
53
+ }
54
+
55
+ export const createGitTool = (cwd: string): Tool => {
56
+ const definition: ToolDefinition = {
57
+ name: 'git',
58
+ category: 'Execute',
59
+ group: 'Git',
60
+ description: `Run git commands for repository operations.
61
+
62
+ COMMON USE CASES:
63
+ git({ command: "status" }) # Working tree status
64
+ git({ command: "diff" }) # Unstaged changes
65
+ git({ command: "diff --cached" }) # Staged changes
66
+ git({ command: "log --oneline -5" }) # Recent commits
67
+ git({ command: "show <hash>" }) # Show a specific commit
68
+ git({ command: "branch" }) # List branches
69
+ git({ command: "branch -a" }) # List all branches (including remote)
70
+
71
+ NOTES:
72
+ - Omit the "git" prefix — just pass the subcommand.
73
+ - Use \`cwd\` to run in a subdirectory of the repo.`,
74
+ parameters: {
75
+ command: {
76
+ type: 'string',
77
+ description:
78
+ 'Git subcommand and arguments (e.g. "status", "diff", "log --oneline -5"). Do NOT include the "git " prefix.',
79
+ required: true,
80
+ },
81
+ path: {
82
+ type: 'string',
83
+ description: 'Repository path (alias: cwd). Defaults to working directory.',
84
+ required: false,
85
+ },
86
+ cwd: {
87
+ type: 'string',
88
+ description: 'Working directory (alias: path). Defaults to working directory.',
89
+ required: false,
90
+ },
91
+ },
92
+ }
93
+
94
+ const execute = async (
95
+ input: Record<string, unknown>,
96
+ ): Promise<{ success: boolean; output: string; error?: string }> => {
97
+ let gitCmd = normalizeGitCommand(String(input.command || ''))
98
+
99
+ // Accept both `path` and `cwd` parameters (LLM convention)
100
+ const repoPath = String(input.path || input.cwd || cwd)
101
+
102
+ if (!gitCmd) {
103
+ return {
104
+ success: false,
105
+ output: '',
106
+ error:
107
+ 'command is required. Use: git({ command: "status" }) — pass the subcommand without the "git " prefix.',
108
+ }
109
+ }
110
+
111
+ // Auto-correct common typos in the first subcommand word
112
+ const firstWord = gitCmd.split(/\s+/)[0]!
113
+ if (firstWord && COMMON_TYPOS[firstWord]) {
114
+ gitCmd = gitCmd.replace(firstWord, COMMON_TYPOS[firstWord]!)
115
+ }
116
+
117
+ // Safety: block destructive commands (checked AFTER normalization)
118
+ const destructivePatterns = [
119
+ /^push\b/i,
120
+ /^reset\s+(--hard|--soft|--mixed|--merge|--keep)/i,
121
+ /^(rebase|merge|cherry-pick|revert)\b/i,
122
+ /^branch\s+-[dD]/i,
123
+ /^branch\s+--delete\b/i,
124
+ /^tag\s+-[dD]/i,
125
+ /^(clean|gc|prune)\b/i,
126
+ /^update-ref\b/i,
127
+ /^rm\b/i,
128
+ /^commit\b/i,
129
+ /^checkout\b/i,
130
+ /^fetch\s+.*--force/i,
131
+ /^stash\s+(drop|clear)\b/i,
132
+ /^config\b/i,
133
+ ]
134
+
135
+ for (const pattern of destructivePatterns) {
136
+ if (pattern.test(gitCmd)) {
137
+ return {
138
+ success: false,
139
+ output: '',
140
+ error: `Destructive git command not allowed: "${gitCmd}". Only read-only operations are permitted. If you need to make changes, use the \`edit\` tool for file modifications and git add/commit for staging.`,
141
+ }
142
+ }
143
+ }
144
+
145
+ // Verify the repo path exists
146
+ if (!fs.existsSync(repoPath)) {
147
+ return {
148
+ success: false,
149
+ output: '',
150
+ error: `Repository path does not exist: "${repoPath}". Use a valid directory path.`,
151
+ }
152
+ }
153
+ if (!fs.statSync(repoPath).isDirectory()) {
154
+ return {
155
+ success: false,
156
+ output: '',
157
+ error: `Repository path is not a directory: "${repoPath}".`,
158
+ }
159
+ }
160
+
161
+ return new Promise(resolve => {
162
+ const child = exec(
163
+ `git ${gitCmd}`,
164
+ {
165
+ cwd: repoPath,
166
+ encoding: 'utf-8',
167
+ timeout: 30000,
168
+ maxBuffer: 10 * 1024 * 1024, // 10MB
169
+ },
170
+ (err, stdout, stderr) => {
171
+ if (err) {
172
+ const exitCode = typeof err.code === 'number' ? err.code : -1
173
+ const stderrMsg = stderr?.trim() || ''
174
+ const stdoutMsg = stdout?.trim() || ''
175
+
176
+ // git diff exits with 1 when there are differences — that's not an error
177
+ if ((gitCmd.startsWith('diff') || gitCmd.startsWith('status')) && stdoutMsg) {
178
+ return resolve({ success: true, output: stdoutMsg })
179
+ }
180
+
181
+ let errorMsg = stderrMsg || err.message || String(err)
182
+ const hint = suggestFix(gitCmd, errorMsg)
183
+ if (hint) errorMsg += `\n💡 ${hint}`
184
+
185
+ return resolve({
186
+ success: exitCode === 0 && stdoutMsg ? true : false,
187
+ output: stdoutMsg,
188
+ error: errorMsg,
189
+ })
190
+ }
191
+
192
+ const output = (stdout || '').trim() || '(empty output)'
193
+ return resolve({ success: true, output })
194
+ },
195
+ )
196
+
197
+ // Handle spawn error (e.g. git not installed)
198
+ child.on('error', spawnErr => {
199
+ return resolve({
200
+ success: false,
201
+ output: '',
202
+ error: `Failed to run git: ${spawnErr.message}. Ensure git is installed and in your PATH.`,
203
+ })
204
+ })
205
+ })
206
+ }
207
+
208
+ return { definition, execute }
209
+ }
@@ -0,0 +1,40 @@
1
+ import type { Tool, ToolResult } from '../types.js'
2
+
3
+ /**
4
+ * A tool the model can call to explicitly signal that the task is complete.
5
+ * In loop mode, this stops the automatic continuation and ends the session.
6
+ */
7
+ export const taskCompleteTool: Tool = {
8
+ definition: {
9
+ name: 'task_complete',
10
+ description: `Call this ONLY when you have confirmed at least THREE TIMES that the task is truly complete.
11
+
12
+ ⚠️ TRIPLE CONFIRMATION REQUIRED ⚠️
13
+ Before calling this tool, you MUST verify each of the following three checks:
14
+
15
+ Check 1 — All requested work is done: Have you addressed every item the user asked for?
16
+ Check 2 — All changes are verified: Did you run the relevant tests / build / commands and confirm they pass?
17
+ Check 3 — No remaining steps: Is there nothing left to do? No pending edits, unverified changes, or unanswered questions?
18
+
19
+ Only call this tool after all three checks pass. If any check fails, continue working instead.
20
+
21
+ Use this instead of just describing completion in text — calling this tool explicitly signals the system to stop processing.
22
+
23
+ Examples:
24
+ - After finishing all requested edits AND verifying they work (all 3 checks pass)
25
+ - After completing a research task AND presenting findings (all 3 checks pass)
26
+ - After a planned task is fully executed AND verified (all 3 checks pass)`,
27
+ parameters: {
28
+ summary: {
29
+ type: 'string',
30
+ description:
31
+ 'A brief summary of what was accomplished in this session (include the 3 checks you verified)',
32
+ required: true,
33
+ },
34
+ },
35
+ },
36
+ async execute(input): Promise<ToolResult> {
37
+ const summary = (input.summary as string) || ''
38
+ return { success: true, output: `TASK_COMPLETE: ${summary}` }
39
+ },
40
+ }
@@ -0,0 +1,222 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+ // @ts-expect-error - adm-zip has no types
4
+ import AdmZip from 'adm-zip'
5
+ import { fmtErr } from '../errors.js'
6
+ import type { ToolResult } from '../types.js'
7
+ import { CLAWHUB_API_BASE, SKILLS_DIR } from './constants.js'
8
+ import { generateClawHubSkillContent, sanitizeSkillName } from './skill-content.js'
9
+
10
+ /** Fetch skill info from ClawHub API */
11
+ export async function fetchClawHubSkillInfo(
12
+ slug: string,
13
+ ): Promise<{ displayName: string; summary: string; latestVersion: string } | null> {
14
+ try {
15
+ const response = await fetch(`${CLAWHUB_API_BASE}/skills/${encodeURIComponent(slug)}`, {
16
+ headers: { Accept: 'application/json' },
17
+ })
18
+ if (!response.ok) return null
19
+
20
+ const data = (await response.json()) as {
21
+ skill?: { displayName?: string; summary?: string }
22
+ latestVersion?: { version?: string }
23
+ }
24
+ return {
25
+ displayName: data.skill?.displayName || slug,
26
+ summary: data.skill?.summary || '',
27
+ latestVersion: data.latestVersion?.version || 'latest',
28
+ }
29
+ } catch {
30
+ return null
31
+ }
32
+ }
33
+
34
+ /** Download a ClawHub skill as a ZIP and extract its content */
35
+ export async function downloadClawHubSkill(
36
+ slug: string,
37
+ version?: string,
38
+ ): Promise<{ files: Map<string, string> } | null> {
39
+ try {
40
+ const versionPath = version ? `/${version}` : ''
41
+ const url = `${CLAWHUB_API_BASE}/download?slug=${encodeURIComponent(slug)}${version ? `&version=${encodeURIComponent(version)}` : ''}`
42
+ const response = await fetch(url)
43
+ if (!response.ok) {
44
+ // Try alternative URL patterns
45
+ const altUrl = `${CLAWHUB_API_BASE}/download/${encodeURIComponent(slug)}${versionPath}`
46
+ const altResponse = await fetch(altUrl)
47
+ if (!altResponse.ok) return null
48
+
49
+ const buffer = await altResponse.arrayBuffer()
50
+ return extractZipContent(Buffer.from(buffer))
51
+ }
52
+
53
+ const buffer = await response.arrayBuffer()
54
+ return extractZipContent(Buffer.from(buffer))
55
+ } catch {
56
+ return null
57
+ }
58
+ }
59
+
60
+ /** Extract files from a ZIP buffer */
61
+ export function extractZipContent(buffer: Buffer): { files: Map<string, string> } {
62
+ const zip = new AdmZip(buffer)
63
+ const entries = zip.getEntries()
64
+ const files = new Map<string, string>()
65
+
66
+ for (const entry of entries) {
67
+ if (!entry.isDirectory) {
68
+ files.set(entry.entryName, entry.getData().toString('utf-8'))
69
+ }
70
+ }
71
+
72
+ return { files }
73
+ }
74
+
75
+ /** Find the SKILL.md entry in the downloaded files (case-insensitive) */
76
+ export function findSkillMdInZip(files: Map<string, string>): string | null {
77
+ for (const [filePath] of files) {
78
+ const normalized = filePath.replace(/\\/g, '/').toLowerCase()
79
+ if (normalized.endsWith('/skill.md')) {
80
+ return files.get(filePath) || null
81
+ }
82
+ }
83
+ // Fallback: check root-level SKILL.md
84
+ for (const [filePath] of files) {
85
+ const normalized = filePath.replace(/\\/g, '/')
86
+ if (normalized.toLowerCase() === 'skill.md' || normalized.endsWith('/skill.md')) {
87
+ return files.get(filePath) || null
88
+ }
89
+ }
90
+ return null
91
+ }
92
+
93
+ /** Normalize a ZIP file path, stripping common root directory */
94
+ export function normalizeZipPath(filePath: string, _slug: string): string {
95
+ const normalized = filePath.replace(/\\/g, '/')
96
+ // Strip leading directory that matches the slug or is a common root
97
+ const parts = normalized.split('/')
98
+ if (parts.length > 1) {
99
+ // Remove the first segment if it's a common root
100
+ // ZIPs from ClawHub often have a root folder like "repo-release-notes/"
101
+ return parts.slice(1).join('/')
102
+ }
103
+ return normalized
104
+ }
105
+
106
+ /** Install a skill from ClawHub */
107
+ export async function installFromClawHub(
108
+ cwd: string,
109
+ slug: string,
110
+ version: string | undefined,
111
+ info: { displayName: string; summary: string; latestVersion: string },
112
+ input: Record<string, unknown>,
113
+ ): Promise<ToolResult> {
114
+ const versionToUse = version || info.latestVersion
115
+
116
+ // Download the skill ZIP
117
+ const downloaded = await downloadClawHubSkill(slug, versionToUse)
118
+ if (!downloaded) {
119
+ return {
120
+ success: false,
121
+ output: '',
122
+ error: `Failed to download ClawHub skill "${slug}" (version ${versionToUse}).`,
123
+ }
124
+ }
125
+
126
+ const skillsDir = path.resolve(cwd, SKILLS_DIR)
127
+ const skillName = sanitizeSkillName(slug)
128
+ const skillFilePath = path.join(skillsDir, `${skillName}.md`)
129
+ const skillDirPath = path.join(skillsDir, skillName)
130
+
131
+ // Find the SKILL.md in the ZIP
132
+ const skillMdEntry = findSkillMdInZip(downloaded.files)
133
+
134
+ // Also extract supporting files into a subdirectory
135
+ if (skillMdEntry) {
136
+ // Extract all files into skill subdirectory for supporting files
137
+ try {
138
+ fs.mkdirSync(skillDirPath, { recursive: true })
139
+ for (const [filePath, content] of downloaded.files) {
140
+ // Normalize the path: strip common root directory
141
+ const normalizedPath = normalizeZipPath(filePath, slug)
142
+ if (!normalizedPath || normalizedPath === 'SKILL.md') continue // SKILL.md goes to root
143
+ const fullPath = path.join(skillDirPath, normalizedPath)
144
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true })
145
+ fs.writeFileSync(fullPath, content, 'utf-8')
146
+ }
147
+ } catch (err) {
148
+ // Non-critical: supporting files are optional
149
+ console.warn(
150
+ `[install_skill] Warning: could not extract all supporting files: ${fmtErr(err)}`,
151
+ )
152
+ }
153
+
154
+ // Generate skill file (using the SKILL.md content from the ZIP)
155
+ const description = (input.description as string) || info.summary || slug
156
+ const skillContent = generateClawHubSkillContent(
157
+ slug,
158
+ info.displayName,
159
+ description,
160
+ skillMdEntry,
161
+ )
162
+
163
+ try {
164
+ fs.mkdirSync(skillsDir, { recursive: true })
165
+ fs.writeFileSync(skillFilePath, skillContent, 'utf-8')
166
+ } catch (err) {
167
+ return {
168
+ success: false,
169
+ output: '',
170
+ error: `Failed to write skill file: ${fmtErr(err)}`,
171
+ }
172
+ }
173
+ } else {
174
+ // No SKILL.md found in ZIP, generate a wrapper skill
175
+ const description = (input.description as string) || info.summary || slug
176
+ // Collect all extracted content as a markdown reference
177
+ const filesList = Array.from(downloaded.files.keys())
178
+ .map(f => `- ${f}`)
179
+ .join('\n')
180
+ const combinedContent =
181
+ `## Files in this skill\n\n${filesList}\n\n` +
182
+ Array.from(downloaded.files.entries())
183
+ .map(([name, content]) => `### ${name}\n\n\`\`\`\n${content.slice(0, 2000)}\n\`\`\``)
184
+ .join('\n\n')
185
+
186
+ const skillContent = generateClawHubSkillContent(
187
+ slug,
188
+ info.displayName,
189
+ description,
190
+ combinedContent,
191
+ )
192
+
193
+ try {
194
+ fs.mkdirSync(skillsDir, { recursive: true })
195
+ fs.writeFileSync(skillFilePath, skillContent, 'utf-8')
196
+ } catch (err) {
197
+ return {
198
+ success: false,
199
+ output: '',
200
+ error: `Failed to write skill file: ${fmtErr(err)}`,
201
+ }
202
+ }
203
+ }
204
+
205
+ const relPath = path.relative(cwd, skillFilePath).replace(/\\/g, '/')
206
+ const supportRelPath = skillMdEntry ? path.relative(cwd, skillDirPath).replace(/\\/g, '/') : ''
207
+
208
+ let output = `Installed ClawHub skill "${slug}" (v${versionToUse}) and created skill file "${relPath}".`
209
+ if (supportRelPath) {
210
+ output += `\nSupporting files extracted to "${supportRelPath}/".`
211
+ }
212
+ output += `
213
+
214
+ The skill instructions will be loaded automatically on the next turn.
215
+
216
+ To verify: use \`read\` on "${relPath}" to see the skill content.`
217
+
218
+ return {
219
+ success: true,
220
+ output,
221
+ }
222
+ }
@@ -0,0 +1,4 @@
1
+ export const SKILLS_DIR = '.lonny/skills'
2
+ export const NPM_REGISTRY_URL = 'https://registry.npmjs.org'
3
+ export const CLAWHUB_API_BASE = 'https://clawhub.ai/api/v1'
4
+ export const CLAWHUB_SITE = 'https://clawhub.ai'
@@ -0,0 +1,150 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+ import { fmtErr } from '../errors.js'
4
+ import type { Tool, ToolResult } from '../types.js'
5
+ import { fetchClawHubSkillInfo, installFromClawHub } from './clawhub.js'
6
+ import { SKILLS_DIR } from './constants.js'
7
+ import { fetchNpmPackageInfo, runNpmInstall } from './npm.js'
8
+ import { extractUsageGuide, generateSkillContent, sanitizeSkillName } from './skill-content.js'
9
+
10
+ export function createInstallSkillTool(cwd: string): Tool {
11
+ return {
12
+ definition: {
13
+ name: 'install_skill',
14
+ category: 'Install',
15
+ group: 'Skill',
16
+ description: `Install an npm package or ClawHub skill. This tool:
17
+ 1. For npm packages: fetches info from npm registry, runs \`npm install\`, creates a skill file
18
+ 2. For ClawHub skills: fetches skill metadata from clawhub.ai, downloads the skill bundle, writes SKILL.md
19
+
20
+ After installation, the skill will be automatically loaded on the next conversation turn (the system prompt includes active skills).
21
+
22
+ Use this when you need to use a third-party npm package or install a skill from ClawHub (clawhub.ai).
23
+
24
+ ClawHub skills: pass the skill slug (e.g. \`repo-release-notes\`) as package_name. The tool auto-detects ClawHub skills.`,
25
+ parameters: {
26
+ package_name: {
27
+ type: 'string',
28
+ description:
29
+ 'The npm package name or ClawHub skill slug to install (e.g. "dayjs", "cn-time-parser", "@scope/package", or ClawHub slug like "repo-release-notes")',
30
+ required: true,
31
+ },
32
+ description: {
33
+ type: 'string',
34
+ description:
35
+ 'Optional description of what the package/skill does. If not provided, it will be fetched from the npm registry or ClawHub.',
36
+ required: false,
37
+ },
38
+ usage_guide: {
39
+ type: 'string',
40
+ description:
41
+ 'Optional custom usage guide for the AI. If not provided, it will be auto-generated from the readme or ClawHub SKILL.md. Provide this if the auto-generated guide is insufficient.',
42
+ required: false,
43
+ },
44
+ clawhub: {
45
+ type: 'boolean',
46
+ description:
47
+ "Force ClawHub mode. If true, treats package_name as a ClawHub skill slug. Auto-detected if the package doesn't exist on npm.",
48
+ required: false,
49
+ },
50
+ version: {
51
+ type: 'string',
52
+ description: 'Optional version for ClawHub skill install (defaults to latest).',
53
+ required: false,
54
+ },
55
+ },
56
+ },
57
+ async execute(input): Promise<ToolResult> {
58
+ // ── Auto-correction: if input is a string, wrap it ────────────────
59
+ if (typeof input === 'string') {
60
+ input = { package_name: input }
61
+ }
62
+
63
+ const packageName = input.package_name as string
64
+ if (!packageName || typeof packageName !== 'string') {
65
+ return {
66
+ success: false,
67
+ output: '',
68
+ error: 'package_name is required (string)',
69
+ }
70
+ }
71
+
72
+ const forceClawHub = input.clawhub === true
73
+ const version = input.version as string | undefined
74
+
75
+ // ── Auto-detect: try npm first, fall back to ClawHub ──────────────
76
+ const npmInfo = forceClawHub ? null : await fetchNpmPackageInfo(packageName)
77
+
78
+ // If npm lookup failed and not forced, try ClawHub
79
+ if (!npmInfo && !forceClawHub) {
80
+ const clawhubInfo = await fetchClawHubSkillInfo(packageName)
81
+ if (clawhubInfo) {
82
+ // It's a ClawHub skill — install from ClawHub
83
+ return installFromClawHub(cwd, packageName, version, clawhubInfo, input)
84
+ }
85
+
86
+ return {
87
+ success: false,
88
+ output: '',
89
+ error: `"${packageName}" was not found on npm registry nor on ClawHub. Check the name and try again.`,
90
+ }
91
+ }
92
+
93
+ // If forced ClawHub mode
94
+ if (forceClawHub) {
95
+ const clawhubInfo = await fetchClawHubSkillInfo(packageName)
96
+ if (!clawhubInfo) {
97
+ return {
98
+ success: false,
99
+ output: '',
100
+ error: `"${packageName}" was not found on ClawHub.`,
101
+ }
102
+ }
103
+ return installFromClawHub(cwd, packageName, version, clawhubInfo, input)
104
+ }
105
+
106
+ // ── npm package install path ──────────────────────────────────────
107
+ const description = (input.description as string) || npmInfo?.description || packageName
108
+ const readme = npmInfo?.readme || ''
109
+
110
+ const installError = await runNpmInstall(cwd, packageName)
111
+ if (installError) {
112
+ return {
113
+ success: false,
114
+ output: '',
115
+ error: `npm install failed: ${installError}`,
116
+ }
117
+ }
118
+
119
+ const usageGuide = (input.usage_guide as string) || (readme ? extractUsageGuide(readme) : '')
120
+
121
+ const skillContent = generateSkillContent(packageName, description, usageGuide)
122
+
123
+ const skillsDir = path.resolve(cwd, SKILLS_DIR)
124
+ const skillName = sanitizeSkillName(packageName)
125
+ const skillFilePath = path.join(skillsDir, `${skillName}.md`)
126
+
127
+ try {
128
+ fs.mkdirSync(skillsDir, { recursive: true })
129
+ fs.writeFileSync(skillFilePath, skillContent, 'utf-8')
130
+ } catch (err) {
131
+ return {
132
+ success: false,
133
+ output: '',
134
+ error: `Failed to write skill file: ${fmtErr(err)}`,
135
+ }
136
+ }
137
+
138
+ const relPath = path.relative(cwd, skillFilePath).replace(/\\/g, '/')
139
+
140
+ return {
141
+ success: true,
142
+ output: `Installed npm package "${packageName}" and created skill "${relPath}".
143
+
144
+ The package is now a project dependency. The skill file contains usage instructions for the AI and will be loaded automatically on the next turn.
145
+
146
+ To verify: use \`read\` on "${relPath}" to see the skill content.`,
147
+ }
148
+ },
149
+ }
150
+ }
@@ -0,0 +1,60 @@
1
+ import * as cp from 'node:child_process'
2
+ import { NPM_REGISTRY_URL } from './constants.js'
3
+
4
+ /** Fetch package info from npm registry */
5
+ export async function fetchNpmPackageInfo(
6
+ packageName: string,
7
+ ): Promise<{ description: string; readme: string } | null> {
8
+ try {
9
+ const response = await fetch(`${NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}`, {
10
+ headers: { Accept: 'application/json' },
11
+ })
12
+ if (!response.ok) return null
13
+
14
+ const data = (await response.json()) as {
15
+ description?: string
16
+ readme?: string
17
+ }
18
+ return {
19
+ description: data.description || '',
20
+ readme: data.readme || '',
21
+ }
22
+ } catch {
23
+ return null
24
+ }
25
+ }
26
+
27
+ /** Run npm install in the project directory */
28
+ export async function runNpmInstall(cwd: string, packageName: string): Promise<string | null> {
29
+ return new Promise(resolve => {
30
+ const child = cp.spawn('npm', ['install', packageName], {
31
+ cwd,
32
+ stdio: ['ignore', 'pipe', 'pipe'],
33
+ shell: process.platform === 'win32',
34
+ })
35
+
36
+ let stderr = ''
37
+ child.stderr?.on('data', (data: Buffer) => {
38
+ stderr += data.toString()
39
+ })
40
+
41
+ const timeout = setTimeout(() => {
42
+ child.kill()
43
+ resolve(`Timed out after 60s. ${stderr.slice(0, 500)}`)
44
+ }, 60_000)
45
+
46
+ child.on('close', code => {
47
+ clearTimeout(timeout)
48
+ if (code === 0) {
49
+ resolve(null) // success
50
+ } else {
51
+ resolve(stderr.slice(0, 500) || `npm install exited with code ${code}`)
52
+ }
53
+ })
54
+
55
+ child.on('error', err => {
56
+ clearTimeout(timeout)
57
+ resolve(`Failed to start npm install: ${err.message}`)
58
+ })
59
+ })
60
+ }