@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,7 +1,8 @@
1
- import { execSync } from "node:child_process";
1
+ import { execFile, } from "node:child_process";
2
2
  import { inspect } from "node:util";
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 { convertNullString } from "../utils/zod.js";
6
7
  // default limit
7
8
  const DEFAULT_MAX_RESULTS = 100;
@@ -38,7 +39,7 @@ const inputSchema = z.object({
38
39
  export const createGrepTool = () => {
39
40
  return {
40
41
  toolDef: {
41
- description: `Search files for patterns using ripgrep (rg). Uses glob patterns for file filtering (e.g., "*.ts", "**/*.test.ts"). Auto-detects unbalanced regex patterns and falls back to fixed-string search for safety. Results are automatically limited to prevent overwhelming output using a hybrid approach: ripgrep's --max-count flag limits matches per file for efficiency, and post-processing ensures reasonable result counts. Default limit is ${DEFAULT_MAX_RESULTS} matches. Use maxResults parameter to override this limit.`,
42
+ description: "Search file contents using ripgrep.",
42
43
  inputSchema,
43
44
  },
44
45
  display({ pattern, path, filePattern, recursive, ignoreCase, contextLines, }) {
@@ -47,7 +48,8 @@ export const createGrepTool = () => {
47
48
  ? null
48
49
  : filePattern;
49
50
  // Enhanced tool-init with detailed search parameters
50
- let initMessage = `${style.cyan(inspect(pattern))} in ${style.cyan(path)}`;
51
+ const displayPath = toDisplayPath(path);
52
+ let initMessage = `${style.cyan(inspect(pattern))} in ${style.cyan(displayPath)}`;
51
53
  if (safeFilePattern) {
52
54
  initMessage += ` ${style.dim(`(filter: ${safeFilePattern})`)}`;
53
55
  }
@@ -66,7 +68,11 @@ export const createGrepTool = () => {
66
68
  if (abortSignal?.aborted) {
67
69
  throw new Error("Grep search aborted");
68
70
  }
71
+ // Validate path - default to cwd if not provided
72
+ const effectivePath = typeof path === "string" && path.trim() !== "" ? path : process.cwd();
69
73
  try {
74
+ // Compute likelyUnbalancedRegex once and pass through
75
+ const isLikelyUnbalanced = likelyUnbalancedRegex(pattern);
70
76
  let effectiveLiteral = null;
71
77
  if (literal === true) {
72
78
  effectiveLiteral = true;
@@ -75,23 +81,13 @@ export const createGrepTool = () => {
75
81
  effectiveLiteral = false;
76
82
  }
77
83
  else {
78
- try {
79
- if (likelyUnbalancedRegex(pattern)) {
80
- effectiveLiteral = true;
81
- }
82
- else {
83
- effectiveLiteral = false;
84
- }
85
- }
86
- catch (_err) {
87
- effectiveLiteral = false;
88
- }
84
+ effectiveLiteral = isLikelyUnbalanced;
89
85
  }
90
86
  const effectiveMaxResults = maxResults ?? DEFAULT_MAX_RESULTS;
91
87
  const safeFilePattern = filePattern === "null" || filePattern === "undefined"
92
88
  ? null
93
89
  : filePattern;
94
- const grepResult = grepFilesStructured(pattern, path, {
90
+ const grepResult = await grepFilesStructured(pattern, effectivePath, {
95
91
  recursive,
96
92
  ignoreCase,
97
93
  filePattern: safeFilePattern,
@@ -99,21 +95,22 @@ export const createGrepTool = () => {
99
95
  searchIgnored,
100
96
  literal: effectiveLiteral,
101
97
  maxResults: effectiveMaxResults,
102
- });
98
+ likelyUnbalanced: isLikelyUnbalanced,
99
+ }, abortSignal);
103
100
  return grepResult.rawOutput;
104
101
  }
105
102
  catch (error) {
106
103
  const errorMessage = error.message;
107
- let userFriendlyError = `Error searching for "${pattern}" in ${path}: ${errorMessage}`;
104
+ let userFriendlyError = `Error searching for "${pattern}" in ${effectivePath}: ${errorMessage}`;
108
105
  if (errorMessage.includes("No such file or directory")) {
109
- userFriendlyError = `Path not found: "${path}"`;
106
+ userFriendlyError = `Path not found: "${effectivePath}"`;
110
107
  if (filePattern) {
111
108
  userFriendlyError += ` with file pattern "${filePattern}"`;
112
109
  }
113
110
  userFriendlyError += " - check if the path exists and is accessible";
114
111
  }
115
112
  else if (errorMessage.includes("permission denied")) {
116
- userFriendlyError = `Permission denied accessing "${path}"`;
113
+ userFriendlyError = `Permission denied accessing "${effectivePath}"`;
117
114
  }
118
115
  else if (errorMessage.includes("Regex parse error")) {
119
116
  userFriendlyError = `Invalid search pattern "${pattern}" - try using literal=true for fixed-string search`;
@@ -123,7 +120,92 @@ export const createGrepTool = () => {
123
120
  },
124
121
  };
125
122
  };
126
- export function likelyUnbalancedRegex(pattern) {
123
+ /**
124
+ * Skips past a character class [...], handling escaped characters.
125
+ * Returns the new index after the character class.
126
+ */
127
+ function skipCharacterClass(pattern, startIndex) {
128
+ let i = startIndex + 1;
129
+ while (i < pattern.length && pattern[i] !== "]") {
130
+ if (pattern[i] === "\\")
131
+ i++;
132
+ i++;
133
+ }
134
+ return i;
135
+ }
136
+ /**
137
+ * Parses and validates the content inside braces {..}.
138
+ * Returns true if the brace content is invalid.
139
+ */
140
+ function isInvalidBraceContent(pattern, startIndex) {
141
+ let j = startIndex + 1;
142
+ let hasDigits = false;
143
+ let hasComma = false;
144
+ // Parse content inside braces
145
+ while (j < pattern.length && pattern[j] !== "}") {
146
+ const c = pattern[j];
147
+ if (c >= "0" && c <= "9") {
148
+ hasDigits = true;
149
+ }
150
+ else if (c === "," && !hasComma) {
151
+ hasComma = true;
152
+ }
153
+ else {
154
+ // Invalid character inside braces
155
+ return true;
156
+ }
157
+ j++;
158
+ }
159
+ // No closing brace found
160
+ if (j >= pattern.length) {
161
+ return true;
162
+ }
163
+ // Empty braces {} with no preceding atom are treated as literal
164
+ if (!hasDigits) {
165
+ const prev = startIndex > 0 ? pattern[startIndex - 1] : undefined;
166
+ if (prev !== undefined && /\S/.test(prev)) {
167
+ return true;
168
+ }
169
+ }
170
+ return false;
171
+ }
172
+ /**
173
+ * Check for invalid repetition operators (e.g., {n}, {n,}, {n,m}) outside of character classes.
174
+ * Returns true if any invalid repetition operators are found.
175
+ */
176
+ function hasInvalidRepetition(pattern) {
177
+ for (let i = 0; i < pattern.length; i++) {
178
+ const ch = pattern[i];
179
+ if (ch === "\\") {
180
+ i++; // Skip the next character (escaped)
181
+ continue;
182
+ }
183
+ if (ch === "[") {
184
+ i = skipCharacterClass(pattern, i);
185
+ continue;
186
+ }
187
+ if (ch === "}") {
188
+ // Unmatched closing brace is invalid
189
+ return true;
190
+ }
191
+ if (ch === "{") {
192
+ if (isInvalidBraceContent(pattern, i)) {
193
+ return true;
194
+ }
195
+ // Find closing brace to move past it
196
+ let j = i + 1;
197
+ while (j < pattern.length && pattern[j] !== "}") {
198
+ j++;
199
+ }
200
+ i = j;
201
+ }
202
+ }
203
+ return false;
204
+ }
205
+ /**
206
+ * Count bracket/paren/brace pairs in a regex pattern, excluding character classes.
207
+ */
208
+ function countBrackets(pattern) {
127
209
  const counts = {
128
210
  openParen: 0,
129
211
  closeParen: 0,
@@ -175,91 +257,56 @@ export function likelyUnbalancedRegex(pattern) {
175
257
  }
176
258
  }
177
259
  }
260
+ return counts;
261
+ }
262
+ /**
263
+ * Check if a pattern contains regex metacharacters.
264
+ * Returns true if the pattern likely needs regex mode.
265
+ */
266
+ function containsRegexMetacharacters(pattern) {
267
+ // Regex metacharacters: ^ $ . * + ? [ ] ( ) { } | \
268
+ const metacharacterPattern = /[\^$.*+?[\](){}|\\]/;
269
+ return metacharacterPattern.test(pattern);
270
+ }
271
+ export function likelyUnbalancedRegex(pattern) {
272
+ // First check: unbalanced brackets/parentheses/braces = likely a typo, use literal
273
+ const counts = countBrackets(pattern);
178
274
  // Check for unbalanced brackets, parentheses, and braces
179
275
  const hasUnbalancedBrackets = counts.openBracket !== counts.closeBracket;
180
276
  const hasUnbalancedParens = counts.openParen !== counts.closeParen;
181
277
  const hasUnbalancedBraces = counts.openBrace !== counts.closeBrace;
182
- // Also check for invalid repetition operators (e.g., {n}, {n,}, {n,m}) outside of character classes
183
- let hasInvalidRepetition = false;
184
- {
185
- let escaped2 = false;
186
- let inClass2 = false;
187
- for (let i = 0; i < pattern.length; i++) {
188
- const ch = pattern[i];
189
- if (escaped2) {
190
- escaped2 = false;
191
- continue;
192
- }
193
- if (ch === "\\") {
194
- escaped2 = true;
195
- continue;
196
- }
197
- if (ch === "[" && !inClass2) {
198
- inClass2 = true;
199
- continue;
200
- }
201
- if (ch === "]" && inClass2) {
202
- inClass2 = false;
203
- continue;
204
- }
205
- if (inClass2) {
206
- continue;
207
- }
208
- if (ch === "{") {
209
- let j = i + 1;
210
- let hasDigits = false;
211
- let hasComma = false;
212
- while (j < pattern.length && pattern[j] !== "}") {
213
- const c = pattern[j];
214
- if (c >= "0" && c <= "9") {
215
- hasDigits = true;
216
- }
217
- else if (c === "," && !hasComma) {
218
- hasComma = true;
219
- }
220
- else {
221
- break;
222
- }
223
- j++;
224
- }
225
- if (j >= pattern.length || pattern[j] !== "}") {
226
- hasInvalidRepetition = true;
227
- break;
228
- }
229
- // At this point we have a closing brace at j
230
- if (!hasDigits) {
231
- // Heuristic: treat empty {} as non-quantifier when it doesn't follow a likely atom
232
- const prev = i > 0 ? pattern[i - 1] : undefined;
233
- if (prev !== undefined && /\S/.test(prev)) {
234
- hasInvalidRepetition = true;
235
- break;
236
- }
237
- // else ignore as literal braces
238
- }
239
- }
240
- }
241
- }
242
- return (hasUnbalancedBrackets ||
278
+ // Also check for invalid repetition operators
279
+ const hasInvalidRepetitionFlag = hasInvalidRepetition(pattern);
280
+ // If pattern has unbalanced syntax, treat as literal (user probably meant to type literal)
281
+ if (hasUnbalancedBrackets ||
243
282
  hasUnbalancedParens ||
244
283
  hasUnbalancedBraces ||
245
- hasInvalidRepetition);
284
+ hasInvalidRepetitionFlag) {
285
+ return true;
286
+ }
287
+ // Second check: if pattern has regex metacharacters, treat as regex (return false)
288
+ // This allows ripgrep to handle the pattern as a proper regex
289
+ if (containsRegexMetacharacters(pattern)) {
290
+ return false;
291
+ }
292
+ // Default: simple alphanumeric strings should use literal mode
293
+ return true;
246
294
  }
247
295
  /**
248
- * Search files for patterns using ripgrep
296
+ * Build grep command args array directly
249
297
  *
250
298
  * @param pattern - The regex pattern to search for
251
299
  * @param path - The path to search in
252
300
  * @param options - Additional options for the grep command
253
- * @returns The result of the grep command
301
+ * @returns The args array for the grep command
254
302
  */
255
- export function buildGrepCommand(pattern, path, options = {}) {
303
+ export function buildGrepArgs(pattern, path, options = {}) {
256
304
  const effectiveRecursive = options.recursive === null ? true : options.recursive;
257
305
  const effectiveIgnoreCase = options.ignoreCase === null ? false : options.ignoreCase;
258
306
  const effectiveSearchIgnored = options.searchIgnored === null ? false : options.searchIgnored;
259
307
  const effectiveFilePattern = options.filePattern;
260
308
  const effectiveContextLines = options.contextLines;
261
- // Determine literal handling: if options.literal is explicitly provided, use it.
262
- // If null/undefined, auto-detect unbalanced regexes and prefer fixed-strings.
309
+ // Use the pre-computed likelyUnbalanced result if available
263
310
  let effectiveLiteral;
264
311
  if (options.literal === true) {
265
312
  effectiveLiteral = true;
@@ -267,27 +314,30 @@ export function buildGrepCommand(pattern, path, options = {}) {
267
314
  else if (options.literal === false) {
268
315
  effectiveLiteral = false;
269
316
  }
317
+ else if (options.likelyUnbalanced !== undefined) {
318
+ effectiveLiteral = options.likelyUnbalanced;
319
+ }
270
320
  else {
271
321
  effectiveLiteral = likelyUnbalancedRegex(pattern);
272
322
  }
273
- let command = "rg --line-number";
323
+ const args = ["--json"];
274
324
  if (effectiveRecursive === false) {
275
- command += " --max-depth=0";
325
+ args.push("--max-depth=0");
276
326
  }
277
327
  if (effectiveIgnoreCase) {
278
- command += " --ignore-case";
328
+ args.push("--ignore-case");
279
329
  }
280
330
  if (effectiveContextLines !== null && effectiveContextLines !== undefined) {
281
- command += ` --context=${effectiveContextLines}`;
331
+ args.push(`--context=${effectiveContextLines}`);
282
332
  }
283
333
  if (effectiveFilePattern !== null && effectiveFilePattern !== undefined) {
284
- command += ` --glob=${JSON.stringify(effectiveFilePattern)}`;
334
+ args.push(`--glob=${effectiveFilePattern}`);
285
335
  }
286
336
  if (effectiveSearchIgnored) {
287
- command += " --no-ignore";
337
+ args.push("--no-ignore");
288
338
  }
289
339
  if (effectiveLiteral) {
290
- command += " -F";
340
+ args.push("-F");
291
341
  }
292
342
  // Use ripgrep's --max-count flag to limit matches per file for efficiency
293
343
  // This helps prevent any single file from dominating results
@@ -296,82 +346,88 @@ export function buildGrepCommand(pattern, path, options = {}) {
296
346
  options.maxResults > 0) {
297
347
  // Use a reasonable per-file limit (max 100 per file) to balance efficiency and completeness
298
348
  const perFileLimit = Math.min(options.maxResults, 100);
299
- command += ` --max-count=${perFileLimit}`;
349
+ args.push(`--max-count=${perFileLimit}`);
300
350
  }
301
- command += ` ${JSON.stringify(pattern)}`;
302
- command += ` ${JSON.stringify(path)}`;
303
- return command;
351
+ args.push(pattern);
352
+ args.push(path);
353
+ return args;
304
354
  }
305
355
  /**
306
- * Parse ripgrep output and extract structured match information
356
+ * Parse ripgrep JSON output and extract structured match information
307
357
  */
308
- export function parseRipgrepOutput(content) {
309
- if (content === "No matches found.") {
358
+ export function parseRipgrepJsonOutput(content) {
359
+ if (!content || content.trim() === "") {
310
360
  return [];
311
361
  }
312
- const lines = content.trim().split("\n");
313
362
  const parsed = [];
363
+ const lines = content.trim().split("\n");
314
364
  for (const line of lines) {
315
- if (line === "--") {
316
- // Separator between file groups - skip
317
- continue;
318
- }
319
- if (line.trim() === "") {
320
- // Empty line - skip
321
- continue;
322
- }
323
- // Try multi-file format: file:line:content
324
- const multiFileMatch = line.match(/^([^:]+):(\d+):(.+)$/);
325
- if (multiFileMatch) {
326
- parsed.push({
327
- file: multiFileMatch[1],
328
- line: Number.parseInt(multiFileMatch[2], 10),
329
- content: multiFileMatch[3],
330
- isMatch: true,
331
- });
365
+ if (!line.trim()) {
332
366
  continue;
333
367
  }
334
- // Try single-file format: line:content
335
- const singleFileMatch = line.match(/^(\d+):(.+)$/);
336
- if (singleFileMatch) {
337
- parsed.push({
338
- line: Number.parseInt(singleFileMatch[1], 10),
339
- content: singleFileMatch[2],
340
- isMatch: true,
341
- });
342
- continue;
368
+ try {
369
+ const rgResult = JSON.parse(line);
370
+ // Handle different ripgrep message types
371
+ if (rgResult.type === "match") {
372
+ const data = rgResult.data;
373
+ parsed.push({
374
+ file: data.path?.text ?? data.path?.bytes?.toString(),
375
+ line: data.line_number ?? 0,
376
+ content: data.lines?.text ?? data.line ?? "",
377
+ isMatch: true,
378
+ lineNumber: data.line_number,
379
+ absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
380
+ submatches: data.submatches?.map((sm) => ({
381
+ start: sm.start,
382
+ end: sm.end,
383
+ text: sm.text,
384
+ })),
385
+ });
386
+ }
387
+ else if (rgResult.type === "context") {
388
+ const data = rgResult.data;
389
+ parsed.push({
390
+ file: data.path?.text ?? data.path?.bytes?.toString(),
391
+ line: data.line_number ?? 0,
392
+ content: data.lines?.text ?? data.line ?? "",
393
+ isMatch: false,
394
+ isContext: true,
395
+ lineNumber: data.line_number,
396
+ absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
397
+ });
398
+ }
399
+ // Ignore other message types like "begin", "end", "summary"
343
400
  }
344
- // Try context line format: file-line-context
345
- const contextMatch = line.match(/^([^:]+)-(\d+)-(.+)$/);
346
- if (contextMatch) {
347
- parsed.push({
348
- file: contextMatch[1],
349
- line: Number.parseInt(contextMatch[2], 10),
350
- content: contextMatch[3],
351
- isMatch: false,
352
- isContext: true,
353
- });
354
- continue;
401
+ catch { }
402
+ }
403
+ return parsed;
404
+ }
405
+ /**
406
+ * Convert parsed JSON matches back to legacy line-number format for backwards compatibility
407
+ */
408
+ function matchesToLegacyFormat(matches) {
409
+ const lines = [];
410
+ for (const match of matches) {
411
+ const lineNum = match.lineNumber ?? match.line;
412
+ const file = match.file ?? match.absolutePath;
413
+ if (file) {
414
+ if (match.isMatch) {
415
+ lines.push(`${file}:${lineNum}:${match.content}`);
416
+ }
417
+ else if (match.isContext) {
418
+ lines.push(`${file}-${lineNum}-${match.content}`);
419
+ }
355
420
  }
356
- // Try context line format without file: line-context
357
- const contextNoFileMatch = line.match(/^(\d+)-(.+)$/);
358
- if (contextNoFileMatch) {
359
- parsed.push({
360
- line: Number.parseInt(contextNoFileMatch[1], 10),
361
- content: contextNoFileMatch[2],
362
- isMatch: false,
363
- isContext: true,
364
- });
365
- continue;
421
+ else {
422
+ if (match.isMatch) {
423
+ lines.push(`${lineNum}:${match.content}`);
424
+ }
425
+ else if (match.isContext) {
426
+ lines.push(`${lineNum}-${match.content}`);
427
+ }
366
428
  }
367
- // If we get here, it's an unrecognized format - treat as match for backwards compatibility
368
- parsed.push({
369
- content: line,
370
- line: 0,
371
- isMatch: true,
372
- });
373
429
  }
374
- return parsed;
430
+ return lines.join("\n");
375
431
  }
376
432
  /**
377
433
  * Count actual matches (excluding context lines)
@@ -385,21 +441,49 @@ export function countActualMatches(parsed) {
385
441
  export function countContextLines(parsed) {
386
442
  return parsed.filter((match) => match.isContext).length;
387
443
  }
444
+ function isContextNearKeptMatch(matches, index, indicesToKeep, contextWindow) {
445
+ for (let j = index - 1; j >= Math.max(0, index - contextWindow); j--) {
446
+ if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
447
+ return true;
448
+ }
449
+ }
450
+ for (let j = index + 1; j < Math.min(matches.length, index + contextWindow + 1); j++) {
451
+ if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
452
+ return true;
453
+ }
454
+ }
455
+ return false;
456
+ }
388
457
  /**
389
- * Truncate matches to a maximum number of results
458
+ * Truncate matches to a maximum number of results, preserving context lines for kept matches
390
459
  */
391
460
  export function truncateMatches(matches, maxResults) {
392
461
  if (!maxResults || maxResults <= 0) {
393
462
  return { truncated: matches, isTruncated: false };
394
463
  }
395
- const actualMatches = matches.filter((m) => m.isMatch && !m.isContext);
396
- if (actualMatches.length <= maxResults) {
464
+ // Find indices of actual matches (excluding context lines)
465
+ const matchIndices = [];
466
+ for (let i = 0; i < matches.length; i++) {
467
+ if (matches[i].isMatch && !matches[i].isContext) {
468
+ matchIndices.push(i);
469
+ }
470
+ }
471
+ if (matchIndices.length <= maxResults) {
397
472
  return { truncated: matches, isTruncated: false };
398
473
  }
474
+ // Get the indices of matches we want to keep
475
+ const indicesToKeep = new Set();
476
+ for (let i = 0; i < maxResults; i++) {
477
+ indicesToKeep.add(matchIndices[i]);
478
+ }
479
+ // Build truncated result: include all kept matches AND their associated context lines
399
480
  const truncated = [];
400
481
  let matchesKept = 0;
401
- for (const match of matches) {
482
+ const contextWindow = 3; // Include up to 3 context lines around each match
483
+ for (let i = 0; i < matches.length; i++) {
484
+ const match = matches[i];
402
485
  if (match.isMatch && !match.isContext) {
486
+ // This is an actual match
403
487
  if (matchesKept < maxResults) {
404
488
  truncated.push(match);
405
489
  matchesKept++;
@@ -408,58 +492,84 @@ export function truncateMatches(matches, maxResults) {
408
492
  break;
409
493
  }
410
494
  }
495
+ else if (match.isContext) {
496
+ if (isContextNearKeptMatch(matches, i, indicesToKeep, contextWindow)) {
497
+ truncated.push(match);
498
+ }
499
+ }
500
+ else {
501
+ // Other types, include them
502
+ truncated.push(match);
503
+ }
411
504
  }
412
505
  return {
413
506
  truncated,
414
507
  isTruncated: true,
415
508
  };
416
509
  }
417
- /**
418
- * Extract matches from content (backwards compatibility wrapper)
419
- */
420
- export function extractMatches(content) {
421
- const parsed = parseRipgrepOutput(content);
422
- const matches = parsed.filter((match) => match.isMatch && !match.isContext);
423
- // Convert back to original string format for backwards compatibility
424
- return matches.map((match) => {
425
- if (match.file) {
426
- return `${match.file}:${match.line}:${match.content}`;
427
- }
428
- return `${match.line}:${match.content}`;
429
- });
430
- }
431
- export function grepFiles(pattern, path, options = {}) {
432
- const result = grepFilesStructured(pattern, path, options);
433
- return result.rawOutput;
434
- }
435
- export function grepFilesStructured(pattern, path, options = {}) {
510
+ export async function grepFilesStructured(pattern, path, options = {}, abortSignal) {
436
511
  try {
437
- const command = buildGrepCommand(pattern, path, options);
438
- const rawOutput = execSync(command, {
439
- encoding: "utf-8",
440
- stdio: ["pipe", "pipe", "pipe"],
512
+ const args = buildGrepArgs(pattern, path, options);
513
+ // Use execFile for async execution with proper abort signal handling
514
+ const rawOutput = await new Promise((resolve, reject) => {
515
+ const child = execFile("rg", args, {
516
+ encoding: "utf-8",
517
+ stdio: ["pipe", "pipe", "pipe"],
518
+ });
519
+ let stdout = "";
520
+ let stderr = "";
521
+ child.stdout?.on("data", (data) => {
522
+ stdout += data;
523
+ });
524
+ child.stderr?.on("data", (data) => {
525
+ stderr += data;
526
+ });
527
+ child.on("close", (code) => {
528
+ if (code === 0 || code === 1) {
529
+ resolve(stdout);
530
+ }
531
+ else {
532
+ reject(new Error(stderr || `ripgrep exited with code ${code}`));
533
+ }
534
+ });
535
+ child.on("error", (err) => {
536
+ reject(err);
537
+ });
538
+ if (abortSignal) {
539
+ abortSignal.addEventListener("abort", () => {
540
+ child.kill("SIGTERM");
541
+ reject(new Error("Grep search aborted"));
542
+ });
543
+ }
441
544
  });
442
- const parsedMatches = parseRipgrepOutput(rawOutput);
545
+ // Parse JSON output from ripgrep
546
+ const parsedMatches = parseRipgrepJsonOutput(rawOutput);
547
+ // If JSON parsing resulted in no matches, check if it's a "no matches" case
548
+ // (ripgrep --json returns empty for no matches)
549
+ const hasMatches = parsedMatches.length > 0;
443
550
  const matchCount = countActualMatches(parsedMatches);
444
551
  // Use the maxResults from options (which will be set by the execute function)
445
552
  const maxResults = options.maxResults;
446
553
  const { truncated, isTruncated } = truncateMatches(parsedMatches, maxResults);
447
554
  const displayedCount = countActualMatches(truncated);
448
555
  const displayedContextCount = countContextLines(truncated);
556
+ // Convert to legacy format for backwards compatibility
557
+ const legacyOutput = matchesToLegacyFormat(truncated);
558
+ const finalOutput = legacyOutput || "No matches found.";
449
559
  return {
450
- rawOutput,
560
+ rawOutput: finalOutput,
451
561
  parsedMatches: truncated,
452
562
  matchCount,
453
563
  displayedCount,
454
564
  contextCount: displayedContextCount,
455
- hasMatches: matchCount > 0,
565
+ hasMatches,
456
566
  isTruncated,
457
567
  };
458
568
  }
459
569
  catch (error) {
460
570
  const execError = error;
461
- const exitCode = execError?.status;
462
- if (exitCode === 1) {
571
+ const exitCode = execError.code;
572
+ if (exitCode === "1") {
463
573
  return {
464
574
  rawOutput: "No matches found.",
465
575
  parsedMatches: [],
@@ -468,10 +578,8 @@ export function grepFilesStructured(pattern, path, options = {}) {
468
578
  hasMatches: false,
469
579
  };
470
580
  }
471
- if (exitCode === 2) {
472
- const stderrStr = typeof execError.stderr === "string"
473
- ? execError.stderr
474
- : (execError.stderr?.toString("utf-8") ?? execError.message);
581
+ if (exitCode === "2") {
582
+ const stderrStr = execError.message;
475
583
  throw new Error(`Regex parse error in pattern "${pattern}": ${stderrStr}`);
476
584
  }
477
585
  throw new Error(`Error executing ripgrep: ${execError.message}`);