@travisennis/acai 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/README.md +48 -729
  2. package/bin/acai +52 -0
  3. package/dist/agent/index.d.ts +12 -2
  4. package/dist/agent/index.d.ts.map +1 -1
  5. package/dist/agent/index.js +378 -168
  6. package/dist/agent/sub-agent.d.ts +23 -0
  7. package/dist/agent/sub-agent.d.ts.map +1 -0
  8. package/dist/agent/sub-agent.js +109 -0
  9. package/dist/cli/index.d.ts +26 -0
  10. package/dist/cli/index.d.ts.map +1 -0
  11. package/dist/{cli.js → cli/index.js} +84 -76
  12. package/dist/cli/stdin.d.ts +9 -0
  13. package/dist/cli/stdin.d.ts.map +1 -0
  14. package/dist/cli/stdin.js +37 -0
  15. package/dist/commands/copy/index.js +2 -2
  16. package/dist/commands/copy/utils.d.ts.map +1 -1
  17. package/dist/commands/copy/utils.js +15 -13
  18. package/dist/commands/generate-rules/index.d.ts +1 -1
  19. package/dist/commands/generate-rules/index.d.ts.map +1 -1
  20. package/dist/commands/generate-rules/index.js +16 -100
  21. package/dist/commands/generate-rules/service.d.ts +21 -0
  22. package/dist/commands/generate-rules/service.d.ts.map +1 -0
  23. package/dist/commands/generate-rules/service.js +103 -0
  24. package/dist/commands/handoff/index.js +2 -2
  25. package/dist/commands/health/index.js +1 -1
  26. package/dist/commands/health/utils.d.ts.map +1 -1
  27. package/dist/commands/health/utils.js +6 -0
  28. package/dist/commands/history/index.d.ts +1 -1
  29. package/dist/commands/history/index.d.ts.map +1 -1
  30. package/dist/commands/history/index.js +17 -18
  31. package/dist/commands/history/types.d.ts +38 -0
  32. package/dist/commands/history/types.d.ts.map +1 -1
  33. package/dist/commands/history/utils.d.ts.map +1 -1
  34. package/dist/commands/history/utils.js +63 -58
  35. package/dist/commands/init/index.d.ts.map +1 -1
  36. package/dist/commands/init/index.js +3 -8
  37. package/dist/commands/init-project/index.d.ts.map +1 -1
  38. package/dist/commands/init-project/index.js +3 -3
  39. package/dist/commands/init-project/utils.d.ts.map +1 -1
  40. package/dist/commands/init-project/utils.js +10 -2
  41. package/dist/commands/list-tools/index.d.ts.map +1 -1
  42. package/dist/commands/list-tools/index.js +7 -31
  43. package/dist/commands/manager.d.ts +2 -2
  44. package/dist/commands/manager.d.ts.map +1 -1
  45. package/dist/commands/manager.js +57 -33
  46. package/dist/commands/model/index.d.ts.map +1 -1
  47. package/dist/commands/model/index.js +20 -151
  48. package/dist/commands/model/model-panel.d.ts +4 -0
  49. package/dist/commands/model/model-panel.d.ts.map +1 -0
  50. package/dist/commands/model/model-panel.js +144 -0
  51. package/dist/commands/paste/index.d.ts.map +1 -1
  52. package/dist/commands/paste/index.js +59 -62
  53. package/dist/commands/paste/utils.d.ts.map +1 -1
  54. package/dist/commands/paste/utils.js +88 -58
  55. package/dist/commands/pickup/index.d.ts.map +1 -1
  56. package/dist/commands/pickup/index.js +6 -3
  57. package/dist/commands/pickup/utils.js +3 -3
  58. package/dist/commands/resources/index.d.ts.map +1 -1
  59. package/dist/commands/resources/index.js +33 -50
  60. package/dist/commands/review/index.d.ts.map +1 -1
  61. package/dist/commands/review/index.js +3 -117
  62. package/dist/commands/review/review-panel.d.ts +3 -0
  63. package/dist/commands/review/review-panel.d.ts.map +1 -0
  64. package/dist/commands/review/review-panel.js +186 -0
  65. package/dist/commands/review/utils.d.ts +9 -0
  66. package/dist/commands/review/utils.d.ts.map +1 -1
  67. package/dist/commands/review/utils.js +127 -68
  68. package/dist/commands/session/index.d.ts +1 -1
  69. package/dist/commands/session/index.d.ts.map +1 -1
  70. package/dist/commands/session/index.js +134 -112
  71. package/dist/commands/session/types.d.ts +7 -0
  72. package/dist/commands/session/types.d.ts.map +1 -1
  73. package/dist/commands/share/html-renderer.d.ts +25 -0
  74. package/dist/commands/share/html-renderer.d.ts.map +1 -0
  75. package/dist/commands/share/html-renderer.js +384 -0
  76. package/dist/commands/share/index.d.ts +3 -0
  77. package/dist/commands/share/index.d.ts.map +1 -0
  78. package/dist/commands/share/index.js +122 -0
  79. package/dist/commands/shell/index.d.ts.map +1 -1
  80. package/dist/commands/shell/index.js +16 -1
  81. package/dist/commands/types.d.ts +2 -2
  82. package/dist/commands/types.d.ts.map +1 -1
  83. package/dist/{config.d.ts → config/index.d.ts} +20 -9
  84. package/dist/config/index.d.ts.map +1 -0
  85. package/dist/{config.js → config/index.js} +43 -42
  86. package/dist/execution/index.d.ts.map +1 -1
  87. package/dist/execution/index.js +75 -55
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +170 -127
  91. package/dist/middleware/cache.d.ts.map +1 -1
  92. package/dist/middleware/cache.js +18 -36
  93. package/dist/models/ai-config.d.ts +1 -0
  94. package/dist/models/ai-config.d.ts.map +1 -1
  95. package/dist/models/ai-config.js +4 -3
  96. package/dist/models/anthropic-provider.d.ts +2 -5
  97. package/dist/models/anthropic-provider.d.ts.map +1 -1
  98. package/dist/models/anthropic-provider.js +3 -70
  99. package/dist/models/deepseek-provider.d.ts +1 -0
  100. package/dist/models/deepseek-provider.d.ts.map +1 -1
  101. package/dist/models/google-provider.d.ts +2 -3
  102. package/dist/models/google-provider.d.ts.map +1 -1
  103. package/dist/models/google-provider.js +0 -26
  104. package/dist/models/groq-provider.d.ts +1 -0
  105. package/dist/models/groq-provider.d.ts.map +1 -1
  106. package/dist/models/manager.d.ts +13 -2
  107. package/dist/models/manager.d.ts.map +1 -1
  108. package/dist/models/manager.js +20 -8
  109. package/dist/models/openai-provider.d.ts +5 -5
  110. package/dist/models/openai-provider.d.ts.map +1 -1
  111. package/dist/models/openai-provider.js +27 -40
  112. package/dist/models/opencode-zen-provider.d.ts +8 -3
  113. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  114. package/dist/models/opencode-zen-provider.js +68 -11
  115. package/dist/models/openrouter-provider.d.ts +24 -30
  116. package/dist/models/openrouter-provider.d.ts.map +1 -1
  117. package/dist/models/openrouter-provider.js +92 -177
  118. package/dist/models/providers.d.ts +1 -1
  119. package/dist/models/providers.d.ts.map +1 -1
  120. package/dist/models/xai-provider.d.ts +4 -3
  121. package/dist/models/xai-provider.d.ts.map +1 -1
  122. package/dist/models/xai-provider.js +18 -18
  123. package/dist/modes/manager.d.ts +23 -0
  124. package/dist/modes/manager.d.ts.map +1 -0
  125. package/dist/modes/manager.js +77 -0
  126. package/dist/modes/prompts.d.ts +2 -0
  127. package/dist/modes/prompts.d.ts.map +1 -0
  128. package/dist/modes/prompts.js +143 -0
  129. package/dist/prompts/mentions.d.ts +11 -0
  130. package/dist/prompts/mentions.d.ts.map +1 -0
  131. package/dist/{mentions.js → prompts/mentions.js} +21 -80
  132. package/dist/prompts/system-prompt.d.ts +26 -0
  133. package/dist/prompts/system-prompt.d.ts.map +1 -0
  134. package/dist/{prompts.js → prompts/system-prompt.js} +50 -22
  135. package/dist/repl/index.d.ts +174 -0
  136. package/dist/repl/index.d.ts.map +1 -0
  137. package/dist/{repl-new.js → repl/index.js} +399 -76
  138. package/dist/repl/project-status.d.ts +1 -0
  139. package/dist/repl/project-status.d.ts.map +1 -1
  140. package/dist/repl/project-status.js +4 -1
  141. package/dist/sessions/manager.d.ts +93 -1
  142. package/dist/sessions/manager.d.ts.map +1 -1
  143. package/dist/sessions/manager.js +264 -9
  144. package/dist/sessions/summary.d.ts +4 -0
  145. package/dist/sessions/summary.d.ts.map +1 -0
  146. package/dist/sessions/summary.js +30 -0
  147. package/dist/{skills.d.ts → skills/index.d.ts} +14 -2
  148. package/dist/skills/index.d.ts.map +1 -0
  149. package/dist/skills/index.js +294 -0
  150. package/dist/subagents/index.d.ts +15 -0
  151. package/dist/subagents/index.d.ts.map +1 -0
  152. package/dist/subagents/index.js +231 -0
  153. package/dist/terminal/control.d.ts +1 -1
  154. package/dist/terminal/control.d.ts.map +1 -1
  155. package/dist/terminal/control.js +30 -9
  156. package/dist/terminal/east-asian-width.d.ts.map +1 -1
  157. package/dist/terminal/east-asian-width.js +404 -351
  158. package/dist/terminal/keys.d.ts +17 -0
  159. package/dist/terminal/keys.d.ts.map +1 -1
  160. package/dist/terminal/keys.js +37 -0
  161. package/dist/terminal/select-prompt.d.ts.map +1 -1
  162. package/dist/terminal/select-prompt.js +24 -12
  163. package/dist/terminal/string-width.d.ts.map +1 -1
  164. package/dist/terminal/string-width.js +25 -27
  165. package/dist/terminal/style.d.ts.map +1 -1
  166. package/dist/terminal/style.js +4 -7
  167. package/dist/terminal/supports-color.d.ts.map +1 -1
  168. package/dist/terminal/supports-color.js +41 -27
  169. package/dist/terminal/table/cell.d.ts +12 -0
  170. package/dist/terminal/table/cell.d.ts.map +1 -1
  171. package/dist/terminal/table/cell.js +40 -25
  172. package/dist/terminal/table/layout-manager.d.ts.map +1 -1
  173. package/dist/terminal/table/layout-manager.js +100 -68
  174. package/dist/terminal/table/utils.d.ts.map +1 -1
  175. package/dist/terminal/table/utils.js +17 -10
  176. package/dist/terminal/wrap-ansi.d.ts.map +1 -1
  177. package/dist/terminal/wrap-ansi.js +172 -103
  178. package/dist/tokens/tracker.d.ts +1 -0
  179. package/dist/tokens/tracker.d.ts.map +1 -1
  180. package/dist/tokens/tracker.js +3 -0
  181. package/dist/tools/agent.d.ts +27 -0
  182. package/dist/tools/agent.d.ts.map +1 -0
  183. package/dist/tools/agent.js +81 -0
  184. package/dist/tools/bash.d.ts +4 -3
  185. package/dist/tools/bash.d.ts.map +1 -1
  186. package/dist/tools/bash.js +343 -121
  187. package/dist/tools/code-search.d.ts +41 -0
  188. package/dist/tools/code-search.d.ts.map +1 -0
  189. package/dist/tools/code-search.js +195 -0
  190. package/dist/tools/directory-tree.d.ts +3 -3
  191. package/dist/tools/directory-tree.d.ts.map +1 -1
  192. package/dist/tools/directory-tree.js +8 -5
  193. package/dist/tools/dynamic-tool-loader.d.ts +2 -5
  194. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  195. package/dist/tools/dynamic-tool-loader.js +20 -4
  196. package/dist/tools/edit-file.d.ts +7 -7
  197. package/dist/tools/edit-file.d.ts.map +1 -1
  198. package/dist/tools/edit-file.js +164 -66
  199. package/dist/tools/glob.d.ts +6 -6
  200. package/dist/tools/glob.d.ts.map +1 -1
  201. package/dist/tools/glob.js +95 -55
  202. package/dist/tools/grep.d.ts +15 -12
  203. package/dist/tools/grep.d.ts.map +1 -1
  204. package/dist/tools/grep.js +300 -192
  205. package/dist/tools/index.d.ts +143 -5
  206. package/dist/tools/index.d.ts.map +1 -1
  207. package/dist/tools/index.js +39 -24
  208. package/dist/tools/ls.d.ts +2 -2
  209. package/dist/tools/ls.d.ts.map +1 -1
  210. package/dist/tools/ls.js +7 -5
  211. package/dist/tools/read-file.d.ts +3 -3
  212. package/dist/tools/read-file.d.ts.map +1 -1
  213. package/dist/tools/read-file.js +74 -34
  214. package/dist/tools/save-file.d.ts +3 -3
  215. package/dist/tools/save-file.d.ts.map +1 -1
  216. package/dist/tools/save-file.js +11 -11
  217. package/dist/tools/skill.d.ts +23 -0
  218. package/dist/tools/skill.d.ts.map +1 -0
  219. package/dist/tools/skill.js +65 -0
  220. package/dist/tools/think.d.ts.map +1 -1
  221. package/dist/tools/think.js +2 -9
  222. package/dist/tools/utils.d.ts +2 -0
  223. package/dist/tools/utils.d.ts.map +1 -1
  224. package/dist/tools/utils.js +12 -0
  225. package/dist/tools/web-fetch.d.ts +62 -0
  226. package/dist/tools/web-fetch.d.ts.map +1 -0
  227. package/dist/tools/web-fetch.js +429 -0
  228. package/dist/tools/web-search.d.ts +62 -0
  229. package/dist/tools/web-search.d.ts.map +1 -0
  230. package/dist/tools/web-search.js +226 -0
  231. package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
  232. package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
  233. package/dist/tui/autocomplete/attachment-provider.js +25 -78
  234. package/dist/tui/autocomplete/base-provider.d.ts +1 -0
  235. package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
  236. package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
  237. package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
  238. package/dist/tui/autocomplete/combined-provider.js +3 -17
  239. package/dist/tui/autocomplete/command-provider.d.ts +1 -0
  240. package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
  241. package/dist/tui/autocomplete/command-provider.js +3 -0
  242. package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
  243. package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
  244. package/dist/tui/autocomplete/file-search-provider.js +36 -16
  245. package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
  246. package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
  247. package/dist/tui/autocomplete/skill-provider.js +49 -0
  248. package/dist/tui/autocomplete.d.ts +2 -2
  249. package/dist/tui/autocomplete.d.ts.map +1 -1
  250. package/dist/tui/autocomplete.js +3 -5
  251. package/dist/tui/components/assistant-message.d.ts.map +1 -1
  252. package/dist/tui/components/assistant-message.js +0 -4
  253. package/dist/tui/components/editor.d.ts +21 -2
  254. package/dist/tui/components/editor.d.ts.map +1 -1
  255. package/dist/tui/components/editor.js +228 -236
  256. package/dist/tui/components/footer.d.ts +6 -4
  257. package/dist/tui/components/footer.d.ts.map +1 -1
  258. package/dist/tui/components/footer.js +49 -25
  259. package/dist/tui/components/markdown.d.ts +8 -5
  260. package/dist/tui/components/markdown.d.ts.map +1 -1
  261. package/dist/tui/components/markdown.js +57 -39
  262. package/dist/tui/components/modal.d.ts.map +1 -1
  263. package/dist/tui/components/modal.js +35 -33
  264. package/dist/tui/components/notification.d.ts +13 -2
  265. package/dist/tui/components/notification.d.ts.map +1 -1
  266. package/dist/tui/components/notification.js +37 -2
  267. package/dist/tui/components/progress-bar.js +1 -1
  268. package/dist/tui/components/select-list.d.ts +1 -0
  269. package/dist/tui/components/select-list.d.ts.map +1 -1
  270. package/dist/tui/components/select-list.js +14 -11
  271. package/dist/tui/components/text.d.ts +16 -0
  272. package/dist/tui/components/text.d.ts.map +1 -1
  273. package/dist/tui/components/text.js +72 -57
  274. package/dist/tui/components/thinking-block.d.ts +9 -0
  275. package/dist/tui/components/thinking-block.d.ts.map +1 -1
  276. package/dist/tui/components/thinking-block.js +43 -11
  277. package/dist/tui/components/tool-execution.d.ts +5 -1
  278. package/dist/tui/components/tool-execution.d.ts.map +1 -1
  279. package/dist/tui/components/tool-execution.js +19 -10
  280. package/dist/tui/components/user-message.d.ts.map +1 -1
  281. package/dist/tui/components/user-message.js +0 -3
  282. package/dist/tui/components/welcome.js +2 -2
  283. package/dist/tui/editor-launcher.d.ts +13 -0
  284. package/dist/tui/editor-launcher.d.ts.map +1 -0
  285. package/dist/tui/editor-launcher.js +39 -0
  286. package/dist/tui/index.d.ts +3 -1
  287. package/dist/tui/index.d.ts.map +1 -1
  288. package/dist/tui/index.js +1 -0
  289. package/dist/tui/terminal.d.ts +27 -0
  290. package/dist/tui/terminal.d.ts.map +1 -1
  291. package/dist/tui/terminal.js +144 -15
  292. package/dist/tui/tui.d.ts +43 -0
  293. package/dist/tui/tui.d.ts.map +1 -1
  294. package/dist/tui/tui.js +172 -41
  295. package/dist/utils/bash/parse.d.ts +19 -0
  296. package/dist/utils/bash/parse.d.ts.map +1 -0
  297. package/dist/utils/bash/parse.js +223 -0
  298. package/dist/utils/bash/quote.d.ts +6 -0
  299. package/dist/utils/bash/quote.d.ts.map +1 -0
  300. package/dist/utils/bash/quote.js +23 -0
  301. package/dist/utils/bash.d.ts.map +1 -1
  302. package/dist/utils/bash.js +211 -126
  303. package/dist/utils/command-protection.d.ts +28 -0
  304. package/dist/utils/command-protection.d.ts.map +1 -0
  305. package/dist/utils/command-protection.js +324 -0
  306. package/dist/utils/dedent.d.ts.map +1 -0
  307. package/dist/utils/env-expand.d.ts +2 -0
  308. package/dist/utils/env-expand.d.ts.map +1 -0
  309. package/dist/utils/env-expand.js +8 -0
  310. package/dist/utils/filesystem/path-display.d.ts +11 -0
  311. package/dist/utils/filesystem/path-display.d.ts.map +1 -0
  312. package/dist/utils/filesystem/path-display.js +32 -0
  313. package/dist/utils/filesystem/security.d.ts +2 -2
  314. package/dist/utils/filesystem/security.d.ts.map +1 -1
  315. package/dist/utils/filesystem/security.js +32 -31
  316. package/dist/utils/formatting.d.ts.map +1 -0
  317. package/dist/{formatting.js → utils/formatting.js} +1 -1
  318. package/dist/utils/git.d.ts +4 -0
  319. package/dist/utils/git.d.ts.map +1 -1
  320. package/dist/utils/git.js +30 -0
  321. package/dist/utils/glob.d.ts +1 -1
  322. package/dist/utils/glob.d.ts.map +1 -1
  323. package/dist/utils/logger.d.ts.map +1 -0
  324. package/dist/{logger.js → utils/logger.js} +1 -1
  325. package/dist/utils/parsing.d.ts.map +1 -0
  326. package/dist/utils/process.d.ts.map +1 -1
  327. package/dist/utils/process.js +90 -37
  328. package/dist/utils/templates.d.ts +2 -0
  329. package/dist/utils/templates.d.ts.map +1 -0
  330. package/dist/utils/templates.js +24 -0
  331. package/dist/utils/version.d.ts.map +1 -0
  332. package/dist/{version.js → utils/version.js} +1 -1
  333. package/package.json +34 -25
  334. package/dist/cli.d.ts +0 -23
  335. package/dist/cli.d.ts.map +0 -1
  336. package/dist/commands/exit/index.d.ts +0 -10
  337. package/dist/commands/exit/index.d.ts.map +0 -1
  338. package/dist/commands/exit/index.js +0 -21
  339. package/dist/commands/exit/types.d.ts +0 -8
  340. package/dist/commands/exit/types.d.ts.map +0 -1
  341. package/dist/commands/exit/types.js +0 -1
  342. package/dist/commands/exit/utils.d.ts +0 -2
  343. package/dist/commands/exit/utils.d.ts.map +0 -1
  344. package/dist/commands/exit/utils.js +0 -13
  345. package/dist/commands/prompt/index.d.ts +0 -5
  346. package/dist/commands/prompt/index.d.ts.map +0 -1
  347. package/dist/commands/prompt/index.js +0 -126
  348. package/dist/commands/prompt/types.d.ts +0 -15
  349. package/dist/commands/prompt/types.d.ts.map +0 -1
  350. package/dist/commands/prompt/types.js +0 -1
  351. package/dist/commands/prompt/utils.d.ts +0 -12
  352. package/dist/commands/prompt/utils.d.ts.map +0 -1
  353. package/dist/commands/prompt/utils.js +0 -107
  354. package/dist/commands/reset/index.d.ts +0 -3
  355. package/dist/commands/reset/index.d.ts.map +0 -1
  356. package/dist/commands/reset/index.js +0 -25
  357. package/dist/commands/reset/types.d.ts +0 -1
  358. package/dist/commands/reset/types.d.ts.map +0 -1
  359. package/dist/commands/reset/types.js +0 -3
  360. package/dist/commands/save/index.d.ts +0 -3
  361. package/dist/commands/save/index.d.ts.map +0 -1
  362. package/dist/commands/save/index.js +0 -19
  363. package/dist/config.d.ts.map +0 -1
  364. package/dist/dedent.d.ts.map +0 -1
  365. package/dist/formatting.d.ts.map +0 -1
  366. package/dist/logger.d.ts.map +0 -1
  367. package/dist/mentions.d.ts +0 -14
  368. package/dist/mentions.d.ts.map +0 -1
  369. package/dist/parsing.d.ts.map +0 -1
  370. package/dist/prompts.d.ts +0 -10
  371. package/dist/prompts.d.ts.map +0 -1
  372. package/dist/repl-new.d.ts +0 -62
  373. package/dist/repl-new.d.ts.map +0 -1
  374. package/dist/skills.d.ts.map +0 -1
  375. package/dist/skills.js +0 -233
  376. package/dist/tui/autocomplete/path-provider.d.ts +0 -21
  377. package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
  378. package/dist/tui/autocomplete/path-provider.js +0 -164
  379. package/dist/version.d.ts.map +0 -1
  380. /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
  381. /package/dist/{dedent.js → utils/dedent.js} +0 -0
  382. /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
  383. /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
  384. /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
  385. /package/dist/{parsing.js → utils/parsing.js} +0 -0
  386. /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
@@ -1,46 +1,73 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { access, constants, readFile, writeFile } from "node:fs/promises";
2
2
  import { createTwoFilesPatch } from "diff";
3
3
  import { z } from "zod";
4
- import { config } from "../config.js";
4
+ import { config } from "../config/index.js";
5
5
  import { clearProjectStatusCache } from "../repl/project-status.js";
6
6
  import style from "../terminal/style.js";
7
+ import { toDisplayPath } from "../utils/filesystem/path-display.js";
7
8
  import { joinWorkingDir, validateFileNotReadOnly, validatePath, } from "../utils/filesystem/security.js";
8
9
  export const EditFileTool = {
9
10
  name: "Edit",
10
11
  };
12
+ const MAX_EDITS_PER_CALL = 10;
11
13
  const inputSchema = z.object({
12
14
  path: z.string().describe("The path of the file to edit."),
13
- edits: z.array(z.object({
15
+ edits: z
16
+ .preprocess((val) => {
17
+ // Handle case where model passes a JSON string instead of an array
18
+ if (typeof val === "string") {
19
+ const trimmed = val.trim();
20
+ // Try parsing as JSON if it looks like an array
21
+ if (trimmed.startsWith("[")) {
22
+ try {
23
+ const parsed = JSON.parse(trimmed);
24
+ if (Array.isArray(parsed)) {
25
+ return parsed;
26
+ }
27
+ }
28
+ catch {
29
+ // Not valid JSON, treat as a plain string
30
+ }
31
+ }
32
+ }
33
+ return val;
34
+ }, z.array(z.object({
14
35
  oldText: z
15
36
  .string()
16
- .describe("Text to search for - must match exactly and enough context must be provided to uniquely match the target text. " +
17
- "Special characters require JSON escaping: backticks (\\`...\\`), quotes, backslashes. " +
18
- "For multi-line content, include exact newlines and indentation."),
37
+ .describe("Text to search for - must match exactly. The oldText must uniquely identify the location - include enough surrounding context (e.g., 3+ lines or function/class names) to ensure only ONE match exists in the file. " +
38
+ "Special characters require JSON escaping: backticks (`\\``...\\``), quotes, backslashes. " +
39
+ "For multi-line content, include exact newlines and indentation.")
40
+ .min(1, "oldText must be at least 1 character"),
19
41
  newText: z.string().describe("Text to replace with"),
20
- })),
42
+ })))
43
+ .describe("The edits to make to the file."),
21
44
  });
22
- export const createEditFileTool = async ({ workingDir, allowedDirs, }) => {
23
- const allowedDirectory = allowedDirs ?? [workingDir];
45
+ export const createEditFileTool = async (options) => {
46
+ const { primaryDir, allowedDirs } = options.workspace;
47
+ const allowedDirectory = allowedDirs ?? [primaryDir];
48
+ // Cache config at tool creation time instead of on every execute
49
+ const projectConfig = await config.getConfig();
24
50
  return {
25
51
  toolDef: {
26
- description: "Make line-based edits to a text file. Each edit replaces exact line sequences " +
27
- "with new content. Exact literal matching is used: no whitespace, indentation, escape, or newline normalization is applied when locating matches. " +
28
- "Provide enough context so the match is unique; otherwise the operation errors. Returns a git-style diff showing the changes made. " +
29
- "Only works within allowed directories. " +
30
- "Note: Special characters in oldText must be properly escaped for JSON (e.g., backticks as \\`...\\`). " +
31
- "Multi-line strings require exact character-by-character matching including whitespace.",
52
+ description: "Edit text in files using literal search-and-replace.",
32
53
  inputSchema,
33
54
  },
34
55
  display({ path, edits }) {
35
- return `\n> ${style.cyan(path)} (${edits.length} edit${edits.length === 1 ? "" : "s"})`;
56
+ const displayPath = toDisplayPath(path);
57
+ return `${style.cyan(displayPath)} (${edits.length} edit${edits.length === 1 ? "" : "s"})`;
36
58
  },
37
59
  async execute({ path, edits }, { abortSignal }) {
38
60
  if (abortSignal?.aborted) {
39
61
  throw new Error("File editing aborted");
40
62
  }
41
- const validPath = await validatePath(joinWorkingDir(path, workingDir), allowedDirectory, { abortSignal });
42
- const projectConfig = await config.getConfig();
43
- validateFileNotReadOnly(validPath, projectConfig, workingDir);
63
+ // Check for excessive edits and return helpful message to the model
64
+ if (edits.length > MAX_EDITS_PER_CALL) {
65
+ throw new Error(`Too many edits (${edits.length}). Maximum ${MAX_EDITS_PER_CALL} edits per call. ` +
66
+ "Please split your changes into multiple tool calls. " +
67
+ "For example, if you need to make 20 edits, make 2 calls with 10 edits each.");
68
+ }
69
+ const validPath = await validatePath(joinWorkingDir(path, primaryDir), allowedDirectory, { abortSignal });
70
+ validateFileNotReadOnly(validPath, projectConfig, primaryDir);
44
71
  const result = await applyFileEdits(validPath, edits, false, abortSignal);
45
72
  clearProjectStatusCache();
46
73
  return result;
@@ -48,55 +75,99 @@ export const createEditFileTool = async ({ workingDir, allowedDirs, }) => {
48
75
  };
49
76
  };
50
77
  // file editing and diffing utilities
78
+ /** Detect the line ending used in the content */
79
+ function detectLineEnding(content) {
80
+ const crlfIdx = content.indexOf("\r\n");
81
+ const lfIdx = content.indexOf("\n");
82
+ // If no line endings at all, default to LF
83
+ if (crlfIdx === -1 && lfIdx === -1)
84
+ return "\n";
85
+ // If CRLF exists (and either no LF or CRLF comes first), return CRLF
86
+ if (crlfIdx !== -1 && (lfIdx === -1 || crlfIdx < lfIdx)) {
87
+ return "\r\n";
88
+ }
89
+ return "\n";
90
+ }
91
+ /** Normalize line endings to LF for internal processing */
51
92
  function normalizeLineEndings(text) {
52
- return text.replace(/\r\n/g, "\n");
93
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
53
94
  }
54
- function createUnifiedDiff(originalContent, newContent, filepath = "file") {
55
- // Ensure consistent line endings for diff
56
- const normalizedOriginal = normalizeLineEndings(originalContent);
57
- const normalizedNew = normalizeLineEndings(newContent);
95
+ /** Restore original line endings after processing */
96
+ function restoreLineEndings(text, ending) {
97
+ return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
98
+ }
99
+ /** Strip UTF-8 BOM if present - users won't include invisible BOM in oldText */
100
+ function stripBom(content) {
101
+ return content.startsWith("\uFEFF")
102
+ ? { bom: "\uFEFF", text: content.slice(1) }
103
+ : { bom: "", text: content };
104
+ }
105
+ function createUnifiedDiff(normalizedOriginal, normalizedNew, filepath = "file") {
58
106
  return createTwoFilesPatch(filepath, filepath, normalizedOriginal, normalizedNew, "original", "modified");
59
107
  }
60
- export async function applyFileEdits(filePath, edits, dryRun = false, abortSignal) {
61
- if (abortSignal?.aborted) {
62
- throw new Error("File edit operation aborted");
108
+ async function validateFileReadable(filePath) {
109
+ try {
110
+ await access(filePath, constants.R_OK);
63
111
  }
64
- // Read file content literally with signal
65
- const originalContent = await readFile(filePath, {
66
- encoding: "utf-8",
67
- signal: abortSignal,
68
- });
69
- if (edits.find((edit) => edit.oldText.length === 0)) {
112
+ catch {
113
+ throw new Error(`File not found or not readable: ${filePath}`);
114
+ }
115
+ }
116
+ function validateEdits(edits) {
117
+ if (edits.some((edit) => edit.oldText.length === 0)) {
70
118
  throw new Error("Invalid oldText in edit. The value of oldText must be at least one character");
71
119
  }
72
- // Apply edits sequentially using literal matches (allow multiple matches)
73
- let modifiedContent = originalContent;
120
+ }
121
+ async function applyEditsSequentially(edits, content, abortSignal, filePath) {
122
+ let modifiedContent = content;
74
123
  for (const edit of edits) {
75
124
  if (abortSignal?.aborted) {
76
125
  throw new Error("File edit operation aborted during processing");
77
126
  }
78
- const result = await applyEditWithLlmFix(edit, modifiedContent);
127
+ const result = await applyNormalizedEdit(edit, modifiedContent);
79
128
  if (result.success) {
80
129
  modifiedContent = result.content;
81
130
  }
131
+ else if (result.errorMessage) {
132
+ throw new Error(result.errorMessage);
133
+ }
82
134
  else {
83
- throw new Error("oldText not found in content");
135
+ throw new Error(`Could not find the exact text in ${filePath}. The oldText must match exactly including all whitespace and newlines. ` +
136
+ "Tip: Check for invisible characters, extra/missing whitespace, or line ending differences.");
84
137
  }
85
138
  }
86
- // Create unified diff (createUnifiedDiff normalizes line endings internally for diffing)
87
- const diff = createUnifiedDiff(originalContent, modifiedContent, filePath);
88
- // Format diff with appropriate number of backticks
139
+ return modifiedContent;
140
+ }
141
+ function formatDiff(diff, _filePath) {
89
142
  let numBackticks = 3;
90
143
  while (diff.includes("`".repeat(numBackticks))) {
91
144
  numBackticks++;
92
145
  }
93
- const formattedDiff = `${"`".repeat(numBackticks)} diff\n${diff}\n${"`".repeat(numBackticks)}`;
146
+ return `${"`".repeat(numBackticks)} diff\n${diff}\n${"`".repeat(numBackticks)}`;
147
+ }
148
+ export async function applyFileEdits(filePath, edits, dryRun = false, abortSignal) {
149
+ if (abortSignal?.aborted) {
150
+ throw new Error("File edit operation aborted");
151
+ }
152
+ await validateFileReadable(filePath);
153
+ const rawContent = await readFile(filePath, {
154
+ encoding: "utf-8",
155
+ signal: abortSignal,
156
+ });
157
+ const { bom: originalBom, text: bomStrippedContent } = stripBom(rawContent);
158
+ const originalLineEnding = detectLineEnding(bomStrippedContent);
159
+ const content = normalizeLineEndings(bomStrippedContent);
160
+ validateEdits(edits);
161
+ const modifiedContent = await applyEditsSequentially(edits, content, abortSignal, filePath);
162
+ const finalContentWithLineEndings = restoreLineEndings(modifiedContent, originalLineEnding);
163
+ const finalContent = originalBom + finalContentWithLineEndings;
164
+ const diff = createUnifiedDiff(content, finalContent, filePath);
165
+ const formattedDiff = formatDiff(diff, filePath);
94
166
  if (!dryRun) {
95
167
  if (abortSignal?.aborted) {
96
168
  throw new Error("File edit operation aborted before writing");
97
169
  }
98
- // Write the modified content with signal
99
- await writeFile(filePath, modifiedContent, {
170
+ await writeFile(filePath, finalContent, {
100
171
  encoding: "utf-8",
101
172
  signal: abortSignal,
102
173
  });
@@ -104,37 +175,64 @@ export async function applyFileEdits(filePath, edits, dryRun = false, abortSigna
104
175
  return formattedDiff;
105
176
  }
106
177
  /**
107
- * Applies a single edit
178
+ * Applies a single edit with normalized line endings
179
+ * Returns an error if oldText matches more than one location in the file
180
+ */
181
+ async function applyNormalizedEdit(edit, content) {
182
+ // Normalize line endings to match the normalized content
183
+ const normalizedOldText = normalizeLineEndings(edit.oldText);
184
+ const normalizedNewText = normalizeLineEndings(edit.newText);
185
+ // First, check how many matches exist (without replacing)
186
+ const matchCountResult = countMatches(content, normalizedOldText);
187
+ const matchCount = matchCountResult.count;
188
+ // If more than one match, require unique oldText
189
+ if (matchCount > 1) {
190
+ return {
191
+ success: false,
192
+ content,
193
+ matchCount,
194
+ errorMessage: `oldText matches ${matchCount} locations in the file but should match only 1. ` +
195
+ "Please provide a more specific oldText that includes more surrounding context (e.g., 3+ lines, " +
196
+ "function/class names, or unique surrounding code) to uniquely identify the location you want to edit.",
197
+ };
198
+ }
199
+ // If no matches, return failure
200
+ if (matchCount === 0) {
201
+ return { success: false, content };
202
+ }
203
+ // Exactly one match - apply the edit
204
+ const originalResult = applyLiteralEdit(content, normalizedOldText, normalizedNewText);
205
+ return { success: true, content: originalResult.content };
206
+ }
207
+ /**
208
+ * Count the number of literal matches in content without replacing
108
209
  */
109
- async function applyEditWithLlmFix(edit, content) {
110
- const { oldText, newText } = edit;
111
- // Try the original edit first
112
- const originalResult = applyLiteralEdit(content, oldText, newText);
113
- if (originalResult.matchCount > 0) {
114
- return { success: true, content: originalResult.content };
210
+ function countMatches(content, search) {
211
+ if (search === "") {
212
+ return { count: 0 };
115
213
  }
116
- return { success: false, content };
214
+ // Escape special regex characters for literal matching
215
+ const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
216
+ const regex = new RegExp(escapedSearch, "g");
217
+ // Count matches without modifying content
218
+ const matches = content.match(regex);
219
+ return { count: matches ? matches.length : 0 };
117
220
  }
118
221
  /**
119
222
  * Applies a literal search and replace operation
120
223
  */
121
224
  function applyLiteralEdit(content, search, replace) {
122
- let modifiedContent = content;
225
+ if (search === "") {
226
+ return { matchCount: 0, content };
227
+ }
228
+ // Escape special regex characters for literal matching
229
+ const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
230
+ const regex = new RegExp(escapedSearch, "g");
231
+ // Use replace with callback to count matches while replacing all occurrences
123
232
  let matchCount = 0;
124
- let currentIndex = 0;
125
- while (currentIndex < modifiedContent.length) {
126
- const matchIndex = modifiedContent.indexOf(search, currentIndex);
127
- if (matchIndex === -1) {
128
- break;
129
- }
233
+ const modifiedContent = content.replace(regex, () => {
130
234
  matchCount++;
131
- // Apply the replacement
132
- modifiedContent =
133
- modifiedContent.slice(0, matchIndex) +
134
- replace +
135
- modifiedContent.slice(matchIndex + search.length);
136
- // Move current index past the replacement
137
- currentIndex = matchIndex + replace.length;
138
- }
235
+ return replace;
236
+ });
139
237
  return { matchCount, content: modifiedContent };
140
238
  }
@@ -4,12 +4,12 @@ export declare const GlobTool: {
4
4
  name: "Glob";
5
5
  };
6
6
  export declare const inputSchema: z.ZodObject<{
7
- patterns: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
8
- path: z.ZodString;
7
+ patterns: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
8
+ path: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodString>;
9
9
  gitignore: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
10
10
  recursive: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
11
11
  expandDirectories: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
12
- ignoreFiles: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
12
+ ignoreFiles: z.ZodPipe<z.ZodTransform<{} | null | undefined, unknown>, z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
13
13
  cwd: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedString<unknown>>>;
14
14
  maxResults: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
15
15
  }, z.core.$strip>;
@@ -18,12 +18,12 @@ export declare const createGlobTool: () => {
18
18
  toolDef: {
19
19
  description: string;
20
20
  inputSchema: z.ZodObject<{
21
- patterns: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
22
- path: z.ZodString;
21
+ patterns: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
22
+ path: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodString>;
23
23
  gitignore: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
24
24
  recursive: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
25
25
  expandDirectories: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
26
- ignoreFiles: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
26
+ ignoreFiles: z.ZodPipe<z.ZodTransform<{} | null | undefined, unknown>, z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
27
27
  cwd: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedString<unknown>>>;
28
28
  maxResults: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
29
29
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../source/tools/glob.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAIvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;iBA4BtB,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;gCAMK,eAAe;wGAkBtC,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CA4ErB,CAAC"}
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../source/tools/glob.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA8CvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;iBA0EtB,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;gCAMK,eAAe;wGAmBtC,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CA6CrB,CAAC"}
@@ -2,17 +2,71 @@ import * as fs from "node:fs";
2
2
  import * as nodePath from "node:path";
3
3
  import { z } from "zod";
4
4
  import style from "../terminal/style.js";
5
+ import { toDisplayPath } from "../utils/filesystem/path-display.js";
5
6
  import { glob } from "../utils/glob.js";
6
7
  import { convertNullString } from "../utils/zod.js";
7
8
  const DEFAULT_MAX_RESULTS = 100;
9
+ const MAX_STAT_FILES = 1000;
10
+ async function getFileWithStats(filePath, effectivePath) {
11
+ const fullPath = nodePath.join(effectivePath, filePath);
12
+ try {
13
+ const stats = await fs.promises.stat(fullPath);
14
+ return {
15
+ path: filePath,
16
+ mtime: stats.mtime.getTime(),
17
+ };
18
+ }
19
+ catch {
20
+ return {
21
+ path: filePath,
22
+ mtime: 0,
23
+ };
24
+ }
25
+ }
26
+ function sortFilesByMtime(files) {
27
+ return files
28
+ .sort((a, b) => {
29
+ if (b.mtime !== a.mtime) {
30
+ return b.mtime - a.mtime;
31
+ }
32
+ return a.path.localeCompare(b.path);
33
+ })
34
+ .map((file) => file.path);
35
+ }
36
+ function formatResult(sortedFiles) {
37
+ return sortedFiles.length > 0
38
+ ? sortedFiles.join("\n")
39
+ : "No files found matching the specified patterns.";
40
+ }
8
41
  export const GlobTool = {
9
42
  name: "Glob",
10
43
  };
11
44
  export const inputSchema = z.object({
12
45
  patterns: z
13
- .union([z.string(), z.array(z.string())])
46
+ .preprocess((val) => {
47
+ if (val === null || val === undefined) {
48
+ return "**/*";
49
+ }
50
+ if (typeof val === "string") {
51
+ const trimmed = val.trim();
52
+ if (trimmed.startsWith("[")) {
53
+ try {
54
+ const parsed = JSON.parse(trimmed);
55
+ if (Array.isArray(parsed)) {
56
+ return parsed;
57
+ }
58
+ }
59
+ catch {
60
+ // Not valid JSON, treat as a plain glob string
61
+ }
62
+ }
63
+ }
64
+ return val;
65
+ }, z.union([z.string(), z.array(z.string())]))
14
66
  .describe("Glob patterns to search for (e.g., '*.ts', '**/*.test.ts', 'src/**/*.js')"),
15
- path: z.string().describe("Base directory to search in"),
67
+ path: z
68
+ .preprocess((val) => (val === null || val === undefined ? process.cwd() : val), z.string())
69
+ .describe("Base directory to search in"),
16
70
  gitignore: z
17
71
  .preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
18
72
  .describe("Respect ignore patterns in .gitignore files. (default: true)"),
@@ -23,9 +77,28 @@ export const inputSchema = z.object({
23
77
  .preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
24
78
  .describe("Automatically expand directories to files. (default: true)"),
25
79
  ignoreFiles: z
26
- .union([z.string(), z.array(z.string())])
27
- .nullable()
28
- .describe("Glob patterns to look for ignore files. (default: undefined)"),
80
+ .preprocess((val) => {
81
+ const converted = convertNullString(val);
82
+ if (converted === null) {
83
+ return null;
84
+ }
85
+ if (typeof converted === "string") {
86
+ const trimmed = converted.trim();
87
+ if (trimmed.startsWith("[")) {
88
+ try {
89
+ const parsed = JSON.parse(trimmed);
90
+ if (Array.isArray(parsed)) {
91
+ return parsed;
92
+ }
93
+ }
94
+ catch {
95
+ // Not valid JSON, treat as a plain string
96
+ }
97
+ }
98
+ }
99
+ return converted;
100
+ }, z.union([z.string(), z.array(z.string())]).nullable())
101
+ .describe("Glob patterns to look for ignore files (e.g., '.gitignore'). Pass null to use default behavior."),
29
102
  cwd: z
30
103
  .preprocess((val) => convertNullString(val), z.coerce.string().nullable())
31
104
  .describe("Current working directory override. (default: process.cwd())"),
@@ -36,7 +109,7 @@ export const inputSchema = z.object({
36
109
  export const createGlobTool = () => {
37
110
  return {
38
111
  toolDef: {
39
- description: `Search for files using glob patterns (e.g., *.ts, **/*.test.ts, src/**/*.js). Uses the fast-glob library with support for .gitignore, recursive searching, directory expansion, and automatic result limiting to prevent overwhelming output. Default limit is ${DEFAULT_MAX_RESULTS} files. Use maxResults parameter to override this limit.`,
112
+ description: "Find files by name pattern (e.g., *.ts).",
40
113
  inputSchema,
41
114
  },
42
115
  display({ patterns, path }) {
@@ -44,69 +117,36 @@ export const createGlobTool = () => {
44
117
  const patternStr = patternArray.length === 1
45
118
  ? patternArray[0]
46
119
  : JSON.stringify(patternArray);
47
- return `\n> ${style.cyan(patternStr)} in ${style.cyan(path)}`;
120
+ const displayPath = toDisplayPath(path);
121
+ return `${style.cyan(patternStr)} in ${style.cyan(displayPath)}`;
48
122
  },
49
123
  async execute({ patterns, path, gitignore, recursive, expandDirectories, ignoreFiles, cwd, maxResults, }, { abortSignal }) {
50
124
  if (abortSignal?.aborted) {
51
125
  throw new Error("Glob search aborted");
52
126
  }
127
+ const effectivePath = typeof path === "string" && path.trim() !== "" ? path : process.cwd();
53
128
  const patternArray = Array.isArray(patterns) ? patterns : [patterns];
129
+ const effectiveMaxResults = maxResults ?? DEFAULT_MAX_RESULTS;
54
130
  const globOptions = {
55
131
  cwd: cwd || process.cwd(),
132
+ ...(gitignore !== null && { gitignore }),
133
+ ...(recursive !== null && { recursive }),
134
+ ...(expandDirectories !== null && { expandDirectories }),
135
+ ...(ignoreFiles !== null && { ignoreFiles }),
56
136
  };
57
- if (gitignore !== null) {
58
- globOptions["gitignore"] = gitignore;
59
- }
60
- if (recursive !== null) {
61
- globOptions["recursive"] = recursive;
62
- }
63
- if (expandDirectories !== null) {
64
- globOptions["expandDirectories"] = expandDirectories;
65
- }
66
- if (ignoreFiles !== null) {
67
- globOptions["ignoreFiles"] = ignoreFiles;
68
- }
69
137
  const matchingFiles = await glob(patternArray, {
70
138
  ...globOptions,
71
- cwd: path,
139
+ cwd: effectivePath,
72
140
  });
73
- const filesWithStats = await Promise.all(matchingFiles.map(async (filePath) => {
74
- const fullPath = nodePath.join(path, filePath);
75
- try {
76
- const stats = await fs.promises.stat(fullPath);
77
- return {
78
- path: filePath,
79
- mtime: stats.mtime,
80
- isRecent: Date.now() - stats.mtime.getTime() < 7 * 24 * 60 * 60 * 1000,
81
- };
82
- }
83
- catch {
84
- return {
85
- path: filePath,
86
- mtime: new Date(0),
87
- isRecent: false,
88
- };
89
- }
90
- }));
91
- const sortedFiles = filesWithStats
92
- .sort((a, b) => {
93
- if (a.isRecent && !b.isRecent)
94
- return -1;
95
- if (!a.isRecent && b.isRecent)
96
- return 1;
97
- if (a.isRecent && b.isRecent) {
98
- return b.mtime.getTime() - a.mtime.getTime();
99
- }
100
- return a.path.localeCompare(b.path);
101
- })
102
- .map((file) => file.path);
103
- const effectiveMaxResults = maxResults ?? DEFAULT_MAX_RESULTS;
104
- const limitedFiles = effectiveMaxResults && effectiveMaxResults > 0
141
+ const filesToStat = matchingFiles.length > MAX_STAT_FILES
142
+ ? matchingFiles.slice(0, MAX_STAT_FILES)
143
+ : matchingFiles;
144
+ const filesWithStats = await Promise.all(filesToStat.map((filePath) => getFileWithStats(filePath, effectivePath)));
145
+ const sortedFiles = sortFilesByMtime(filesWithStats);
146
+ const result = effectiveMaxResults > 0 && sortedFiles.length > effectiveMaxResults
105
147
  ? sortedFiles.slice(0, effectiveMaxResults)
106
148
  : sortedFiles;
107
- return limitedFiles.length > 0
108
- ? limitedFiles.join("\n")
109
- : "No files found matching the specified patterns.";
149
+ return formatResult(result);
110
150
  },
111
151
  };
112
152
  };
@@ -41,23 +41,31 @@ interface GrepOptions {
41
41
  searchIgnored?: boolean | null;
42
42
  literal?: boolean | null;
43
43
  maxResults?: number | null;
44
+ likelyUnbalanced?: boolean;
44
45
  }
45
46
  export declare function likelyUnbalancedRegex(pattern: string): boolean;
46
47
  /**
47
- * Search files for patterns using ripgrep
48
+ * Build grep command args array directly
48
49
  *
49
50
  * @param pattern - The regex pattern to search for
50
51
  * @param path - The path to search in
51
52
  * @param options - Additional options for the grep command
52
- * @returns The result of the grep command
53
+ * @returns The args array for the grep command
53
54
  */
54
- export declare function buildGrepCommand(pattern: string, path: string, options?: GrepOptions): string;
55
+ export declare function buildGrepArgs(pattern: string, path: string, options?: GrepOptions): string[];
55
56
  export interface ParsedMatch {
56
57
  file?: string;
57
58
  line: number;
58
59
  content: string;
59
60
  isMatch: boolean;
60
61
  isContext?: boolean;
62
+ lineNumber?: number;
63
+ absolutePath?: string;
64
+ submatches?: Array<{
65
+ start: number;
66
+ end: number;
67
+ text: string;
68
+ }>;
61
69
  }
62
70
  interface GrepResult {
63
71
  rawOutput: string;
@@ -69,9 +77,9 @@ interface GrepResult {
69
77
  isTruncated?: boolean;
70
78
  }
71
79
  /**
72
- * Parse ripgrep output and extract structured match information
80
+ * Parse ripgrep JSON output and extract structured match information
73
81
  */
74
- export declare function parseRipgrepOutput(content: string): ParsedMatch[];
82
+ export declare function parseRipgrepJsonOutput(content: string): ParsedMatch[];
75
83
  /**
76
84
  * Count actual matches (excluding context lines)
77
85
  */
@@ -81,17 +89,12 @@ export declare function countActualMatches(parsed: ParsedMatch[]): number;
81
89
  */
82
90
  export declare function countContextLines(parsed: ParsedMatch[]): number;
83
91
  /**
84
- * Truncate matches to a maximum number of results
92
+ * Truncate matches to a maximum number of results, preserving context lines for kept matches
85
93
  */
86
94
  export declare function truncateMatches(matches: ParsedMatch[], maxResults: number | null | undefined): {
87
95
  truncated: ParsedMatch[];
88
96
  isTruncated: boolean;
89
97
  };
90
- /**
91
- * Extract matches from content (backwards compatibility wrapper)
92
- */
93
- export declare function extractMatches(content: string): string[];
94
- export declare function grepFiles(pattern: string, path: string, options?: GrepOptions): string;
95
- export declare function grepFilesStructured(pattern: string, path: string, options?: GrepOptions): GrepResult;
98
+ export declare function grepFilesStructured(pattern: string, path: string, options?: GrepOptions, abortSignal?: AbortSignal | null): Promise<GrepResult>;
96
99
  export {};
97
100
  //# sourceMappingURL=grep.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../source/tools/grep.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,QAAA,MAAM,WAAW;;;;;;;;;;iBAoCf,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;kFAapB,eAAe;sHAmCb,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CA6DrB,CAAC;AAEF,UAAU,WAAW;IACnB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAQD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAiI9D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,CA+DR;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,WAAW,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CA4EjE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAEhE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAE/D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC;IAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CA6BpD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAWxD;AAED,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,CAGR;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,UAAU,CAwDZ"}
1
+ {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../source/tools/grep.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,QAAA,MAAM,WAAW;;;;;;;;;;iBAoCf,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;kFAapB,eAAe;sHAoCb,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CAkErB,CAAC;AAEF,UAAU,WAAW;IACnB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAqLD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA8B9D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,EAAE,CAgEV;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,WAAW,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CAqDrE;AA8BD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAEhE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAE/D;AAyBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC;IAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAqDpD;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,EACzB,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,GAC/B,OAAO,CAAC,UAAU,CAAC,CAgGrB"}