@travisennis/acai 0.0.9 → 0.0.11

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 (403) hide show
  1. package/README.md +51 -760
  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 +380 -199
  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 -77
  12. package/dist/{stdin.d.ts → cli/stdin.d.ts} +2 -1
  13. package/dist/cli/stdin.d.ts.map +1 -0
  14. package/dist/{stdin.js → cli/stdin.js} +11 -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 -101
  21. package/dist/commands/generate-rules/service.d.ts +22 -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 +3 -2
  27. package/dist/commands/health/utils.d.ts.map +1 -1
  28. package/dist/commands/health/utils.js +6 -0
  29. package/dist/commands/history/index.d.ts +1 -1
  30. package/dist/commands/history/index.d.ts.map +1 -1
  31. package/dist/commands/history/index.js +17 -18
  32. package/dist/commands/history/types.d.ts +38 -0
  33. package/dist/commands/history/types.d.ts.map +1 -1
  34. package/dist/commands/history/utils.d.ts.map +1 -1
  35. package/dist/commands/history/utils.js +63 -58
  36. package/dist/commands/init/index.d.ts.map +1 -1
  37. package/dist/commands/init/index.js +3 -8
  38. package/dist/commands/init-project/index.d.ts.map +1 -1
  39. package/dist/commands/init-project/index.js +3 -3
  40. package/dist/commands/init-project/utils.d.ts +2 -1
  41. package/dist/commands/init-project/utils.d.ts.map +1 -1
  42. package/dist/commands/init-project/utils.js +10 -2
  43. package/dist/commands/list-tools/index.d.ts.map +1 -1
  44. package/dist/commands/list-tools/index.js +7 -31
  45. package/dist/commands/manager.d.ts +2 -2
  46. package/dist/commands/manager.d.ts.map +1 -1
  47. package/dist/commands/manager.js +55 -33
  48. package/dist/commands/model/index.d.ts.map +1 -1
  49. package/dist/commands/model/index.js +20 -151
  50. package/dist/commands/model/model-panel.d.ts +4 -0
  51. package/dist/commands/model/model-panel.d.ts.map +1 -0
  52. package/dist/commands/model/model-panel.js +144 -0
  53. package/dist/commands/paste/index.d.ts.map +1 -1
  54. package/dist/commands/paste/index.js +59 -62
  55. package/dist/commands/paste/utils.d.ts.map +1 -1
  56. package/dist/commands/paste/utils.js +88 -58
  57. package/dist/commands/pickup/index.d.ts.map +1 -1
  58. package/dist/commands/pickup/index.js +6 -3
  59. package/dist/commands/pickup/utils.js +3 -3
  60. package/dist/commands/resources/index.d.ts.map +1 -1
  61. package/dist/commands/resources/index.js +33 -50
  62. package/dist/commands/review/index.d.ts.map +1 -1
  63. package/dist/commands/review/index.js +3 -117
  64. package/dist/commands/review/review-panel.d.ts +3 -0
  65. package/dist/commands/review/review-panel.d.ts.map +1 -0
  66. package/dist/commands/review/review-panel.js +186 -0
  67. package/dist/commands/review/utils.d.ts +15 -1
  68. package/dist/commands/review/utils.d.ts.map +1 -1
  69. package/dist/commands/review/utils.js +127 -68
  70. package/dist/commands/session/index.d.ts +1 -1
  71. package/dist/commands/session/index.d.ts.map +1 -1
  72. package/dist/commands/session/index.js +124 -135
  73. package/dist/commands/shell/index.d.ts.map +1 -1
  74. package/dist/commands/shell/index.js +16 -1
  75. package/dist/commands/types.d.ts +2 -2
  76. package/dist/commands/types.d.ts.map +1 -1
  77. package/dist/{config.d.ts → config/index.d.ts} +20 -9
  78. package/dist/config/index.d.ts.map +1 -0
  79. package/dist/{config.js → config/index.js} +43 -42
  80. package/dist/execution/index.d.ts.map +1 -1
  81. package/dist/execution/index.js +75 -55
  82. package/dist/index.d.ts +1 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +148 -141
  85. package/dist/middleware/cache.d.ts.map +1 -1
  86. package/dist/middleware/cache.js +18 -36
  87. package/dist/models/ai-config.d.ts +1 -0
  88. package/dist/models/ai-config.d.ts.map +1 -1
  89. package/dist/models/ai-config.js +4 -3
  90. package/dist/models/anthropic-provider.d.ts +2 -5
  91. package/dist/models/anthropic-provider.d.ts.map +1 -1
  92. package/dist/models/anthropic-provider.js +3 -70
  93. package/dist/models/deepseek-provider.d.ts +1 -0
  94. package/dist/models/deepseek-provider.d.ts.map +1 -1
  95. package/dist/models/google-provider.d.ts +2 -3
  96. package/dist/models/google-provider.d.ts.map +1 -1
  97. package/dist/models/google-provider.js +0 -26
  98. package/dist/models/groq-provider.d.ts +1 -0
  99. package/dist/models/groq-provider.d.ts.map +1 -1
  100. package/dist/models/manager.d.ts +13 -2
  101. package/dist/models/manager.d.ts.map +1 -1
  102. package/dist/models/manager.js +20 -8
  103. package/dist/models/openai-provider.d.ts +2 -5
  104. package/dist/models/openai-provider.d.ts.map +1 -1
  105. package/dist/models/openai-provider.js +0 -52
  106. package/dist/models/opencode-go-provider.d.ts +25 -0
  107. package/dist/models/opencode-go-provider.d.ts.map +1 -0
  108. package/dist/models/opencode-go-provider.js +78 -0
  109. package/dist/models/opencode-zen-provider.d.ts +7 -3
  110. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  111. package/dist/models/opencode-zen-provider.js +49 -10
  112. package/dist/models/openrouter-provider.d.ts +27 -31
  113. package/dist/models/openrouter-provider.d.ts.map +1 -1
  114. package/dist/models/openrouter-provider.js +121 -180
  115. package/dist/models/providers.d.ts +3 -3
  116. package/dist/models/providers.d.ts.map +1 -1
  117. package/dist/models/providers.js +6 -0
  118. package/dist/models/xai-provider.d.ts +4 -3
  119. package/dist/models/xai-provider.d.ts.map +1 -1
  120. package/dist/models/xai-provider.js +18 -18
  121. package/dist/modes/manager.d.ts +24 -0
  122. package/dist/modes/manager.d.ts.map +1 -0
  123. package/dist/modes/manager.js +77 -0
  124. package/dist/modes/prompts.d.ts +2 -0
  125. package/dist/modes/prompts.d.ts.map +1 -0
  126. package/dist/modes/prompts.js +142 -0
  127. package/dist/prompts/mentions.d.ts +11 -0
  128. package/dist/prompts/mentions.d.ts.map +1 -0
  129. package/dist/{mentions.js → prompts/mentions.js} +55 -85
  130. package/dist/{prompts.d.ts → prompts/system-prompt.d.ts} +7 -2
  131. package/dist/prompts/system-prompt.d.ts.map +1 -0
  132. package/dist/{prompts.js → prompts/system-prompt.js} +31 -16
  133. package/dist/repl/index.d.ts +174 -0
  134. package/dist/repl/index.d.ts.map +1 -0
  135. package/dist/{repl-new.js → repl/index.js} +397 -76
  136. package/dist/repl/project-status.d.ts +1 -0
  137. package/dist/repl/project-status.d.ts.map +1 -1
  138. package/dist/repl/project-status.js +4 -1
  139. package/dist/sessions/manager.d.ts +92 -0
  140. package/dist/sessions/manager.d.ts.map +1 -1
  141. package/dist/sessions/manager.js +262 -9
  142. package/dist/sessions/summary.d.ts +4 -0
  143. package/dist/sessions/summary.d.ts.map +1 -0
  144. package/dist/sessions/summary.js +30 -0
  145. package/dist/skills/index.d.ts +29 -0
  146. package/dist/skills/index.d.ts.map +1 -0
  147. package/dist/skills/index.js +294 -0
  148. package/dist/subagents/index.d.ts +16 -0
  149. package/dist/subagents/index.d.ts.map +1 -0
  150. package/dist/subagents/index.js +231 -0
  151. package/dist/terminal/control.d.ts +1 -1
  152. package/dist/terminal/control.d.ts.map +1 -1
  153. package/dist/terminal/control.js +3 -3
  154. package/dist/terminal/east-asian-width.d.ts.map +1 -1
  155. package/dist/terminal/east-asian-width.js +404 -351
  156. package/dist/terminal/keys.d.ts +17 -0
  157. package/dist/terminal/keys.d.ts.map +1 -1
  158. package/dist/terminal/keys.js +37 -0
  159. package/dist/terminal/select-prompt.d.ts.map +1 -1
  160. package/dist/terminal/select-prompt.js +24 -12
  161. package/dist/terminal/string-width.d.ts.map +1 -1
  162. package/dist/terminal/string-width.js +25 -27
  163. package/dist/terminal/style.d.ts.map +1 -1
  164. package/dist/terminal/style.js +4 -7
  165. package/dist/terminal/supports-color.d.ts.map +1 -1
  166. package/dist/terminal/supports-color.js +41 -27
  167. package/dist/terminal/table/cell.d.ts +12 -0
  168. package/dist/terminal/table/cell.d.ts.map +1 -1
  169. package/dist/terminal/table/cell.js +40 -25
  170. package/dist/terminal/table/layout-manager.d.ts.map +1 -1
  171. package/dist/terminal/table/layout-manager.js +100 -68
  172. package/dist/terminal/table/utils.d.ts +1 -1
  173. package/dist/terminal/table/utils.d.ts.map +1 -1
  174. package/dist/terminal/table/utils.js +17 -10
  175. package/dist/terminal/wrap-ansi.d.ts.map +1 -1
  176. package/dist/terminal/wrap-ansi.js +174 -105
  177. package/dist/tokens/tracker.d.ts +1 -0
  178. package/dist/tokens/tracker.d.ts.map +1 -1
  179. package/dist/tokens/tracker.js +3 -0
  180. package/dist/tools/agent.d.ts +27 -0
  181. package/dist/tools/agent.d.ts.map +1 -0
  182. package/dist/tools/agent.js +81 -0
  183. package/dist/tools/apply-patch.d.ts +62 -0
  184. package/dist/tools/apply-patch.d.ts.map +1 -0
  185. package/dist/tools/apply-patch.js +377 -0
  186. package/dist/tools/bash.d.ts +4 -3
  187. package/dist/tools/bash.d.ts.map +1 -1
  188. package/dist/tools/bash.js +349 -141
  189. package/dist/tools/directory-tree.d.ts +3 -3
  190. package/dist/tools/directory-tree.d.ts.map +1 -1
  191. package/dist/tools/directory-tree.js +8 -5
  192. package/dist/tools/dynamic-tool-loader.d.ts +3 -6
  193. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  194. package/dist/tools/dynamic-tool-loader.js +20 -4
  195. package/dist/tools/edit-file.d.ts +7 -7
  196. package/dist/tools/edit-file.d.ts.map +1 -1
  197. package/dist/tools/edit-file.js +292 -85
  198. package/dist/tools/glob.d.ts +6 -6
  199. package/dist/tools/glob.d.ts.map +1 -1
  200. package/dist/tools/glob.js +110 -63
  201. package/dist/tools/grep.d.ts +15 -12
  202. package/dist/tools/grep.d.ts.map +1 -1
  203. package/dist/tools/grep.js +315 -193
  204. package/dist/tools/index.d.ts +114 -9
  205. package/dist/tools/index.d.ts.map +1 -1
  206. package/dist/tools/index.js +39 -24
  207. package/dist/tools/ls.d.ts +2 -2
  208. package/dist/tools/ls.d.ts.map +1 -1
  209. package/dist/tools/ls.js +7 -5
  210. package/dist/tools/read-file.d.ts +4 -6
  211. package/dist/tools/read-file.d.ts.map +1 -1
  212. package/dist/tools/read-file.js +84 -39
  213. package/dist/tools/save-file.d.ts +3 -3
  214. package/dist/tools/save-file.d.ts.map +1 -1
  215. package/dist/tools/save-file.js +36 -31
  216. package/dist/tools/skill.d.ts +23 -0
  217. package/dist/tools/skill.d.ts.map +1 -0
  218. package/dist/tools/skill.js +65 -0
  219. package/dist/tools/think.d.ts.map +1 -1
  220. package/dist/tools/think.js +2 -9
  221. package/dist/tools/utils.d.ts +2 -0
  222. package/dist/tools/utils.d.ts.map +1 -1
  223. package/dist/tools/utils.js +12 -0
  224. package/dist/tools/web-fetch.d.ts +50 -0
  225. package/dist/tools/web-fetch.d.ts.map +1 -0
  226. package/dist/tools/web-fetch.js +446 -0
  227. package/dist/tools/web-search.d.ts +44 -0
  228. package/dist/tools/web-search.d.ts.map +1 -0
  229. package/dist/tools/web-search.js +226 -0
  230. package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
  231. package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
  232. package/dist/tui/autocomplete/attachment-provider.js +25 -78
  233. package/dist/tui/autocomplete/base-provider.d.ts +1 -0
  234. package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
  235. package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
  236. package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
  237. package/dist/tui/autocomplete/combined-provider.js +3 -17
  238. package/dist/tui/autocomplete/command-provider.d.ts +1 -0
  239. package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
  240. package/dist/tui/autocomplete/command-provider.js +3 -0
  241. package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
  242. package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
  243. package/dist/tui/autocomplete/file-search-provider.js +37 -17
  244. package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
  245. package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
  246. package/dist/tui/autocomplete/skill-provider.js +49 -0
  247. package/dist/tui/autocomplete/utils.d.ts +2 -1
  248. package/dist/tui/autocomplete/utils.d.ts.map +1 -1
  249. package/dist/tui/autocomplete/utils.js +25 -23
  250. package/dist/tui/autocomplete.d.ts +2 -2
  251. package/dist/tui/autocomplete.d.ts.map +1 -1
  252. package/dist/tui/autocomplete.js +3 -5
  253. package/dist/tui/components/assistant-message.d.ts.map +1 -1
  254. package/dist/tui/components/assistant-message.js +0 -4
  255. package/dist/tui/components/editor.d.ts +18 -3
  256. package/dist/tui/components/editor.d.ts.map +1 -1
  257. package/dist/tui/components/editor.js +211 -237
  258. package/dist/tui/components/footer.d.ts +6 -4
  259. package/dist/tui/components/footer.d.ts.map +1 -1
  260. package/dist/tui/components/footer.js +49 -25
  261. package/dist/tui/components/markdown.d.ts +10 -7
  262. package/dist/tui/components/markdown.d.ts.map +1 -1
  263. package/dist/tui/components/markdown.js +57 -39
  264. package/dist/tui/components/modal.d.ts.map +1 -1
  265. package/dist/tui/components/modal.js +35 -33
  266. package/dist/tui/components/notification.d.ts +13 -2
  267. package/dist/tui/components/notification.d.ts.map +1 -1
  268. package/dist/tui/components/notification.js +36 -2
  269. package/dist/tui/components/progress-bar.js +1 -1
  270. package/dist/tui/components/select-list.d.ts +1 -0
  271. package/dist/tui/components/select-list.d.ts.map +1 -1
  272. package/dist/tui/components/select-list.js +14 -11
  273. package/dist/tui/components/text.d.ts +16 -0
  274. package/dist/tui/components/text.d.ts.map +1 -1
  275. package/dist/tui/components/text.js +72 -57
  276. package/dist/tui/components/thinking-block.d.ts +9 -0
  277. package/dist/tui/components/thinking-block.d.ts.map +1 -1
  278. package/dist/tui/components/thinking-block.js +43 -11
  279. package/dist/tui/components/tool-execution.d.ts +5 -1
  280. package/dist/tui/components/tool-execution.d.ts.map +1 -1
  281. package/dist/tui/components/tool-execution.js +19 -10
  282. package/dist/tui/components/user-message.d.ts.map +1 -1
  283. package/dist/tui/components/user-message.js +0 -3
  284. package/dist/tui/components/welcome.d.ts +2 -1
  285. package/dist/tui/components/welcome.d.ts.map +1 -1
  286. package/dist/tui/components/welcome.js +2 -2
  287. package/dist/tui/editor-launcher.d.ts +3 -2
  288. package/dist/tui/editor-launcher.d.ts.map +1 -1
  289. package/dist/tui/index.d.ts +0 -1
  290. package/dist/tui/index.d.ts.map +1 -1
  291. package/dist/tui/terminal.d.ts.map +1 -1
  292. package/dist/tui/terminal.js +10 -2
  293. package/dist/tui/tui.d.ts +43 -0
  294. package/dist/tui/tui.d.ts.map +1 -1
  295. package/dist/tui/tui.js +166 -41
  296. package/dist/tui/utils.d.ts +1 -5
  297. package/dist/tui/utils.d.ts.map +1 -1
  298. package/dist/tui/utils.js +271 -44
  299. package/dist/utils/bash/parse.d.ts +19 -0
  300. package/dist/utils/bash/parse.d.ts.map +1 -0
  301. package/dist/utils/bash/parse.js +223 -0
  302. package/dist/utils/bash/quote.d.ts +6 -0
  303. package/dist/utils/bash/quote.d.ts.map +1 -0
  304. package/dist/utils/bash/quote.js +23 -0
  305. package/dist/utils/bash.d.ts.map +1 -1
  306. package/dist/utils/bash.js +211 -126
  307. package/dist/utils/command-protection.d.ts +28 -0
  308. package/dist/utils/command-protection.d.ts.map +1 -0
  309. package/dist/utils/command-protection.js +324 -0
  310. package/dist/utils/dedent.d.ts.map +1 -0
  311. package/dist/utils/env-expand.d.ts +2 -0
  312. package/dist/utils/env-expand.d.ts.map +1 -0
  313. package/dist/utils/env-expand.js +8 -0
  314. package/dist/utils/filesystem/path-display.d.ts +11 -0
  315. package/dist/utils/filesystem/path-display.d.ts.map +1 -0
  316. package/dist/utils/filesystem/path-display.js +32 -0
  317. package/dist/utils/filesystem/security.d.ts +2 -2
  318. package/dist/utils/filesystem/security.d.ts.map +1 -1
  319. package/dist/utils/filesystem/security.js +28 -30
  320. package/dist/utils/formatting.d.ts.map +1 -0
  321. package/dist/{formatting.js → utils/formatting.js} +1 -1
  322. package/dist/utils/git.d.ts +4 -0
  323. package/dist/utils/git.d.ts.map +1 -1
  324. package/dist/utils/git.js +30 -0
  325. package/dist/utils/glob.d.ts +1 -1
  326. package/dist/utils/glob.d.ts.map +1 -1
  327. package/dist/utils/logger.d.ts.map +1 -0
  328. package/dist/{logger.js → utils/logger.js} +1 -1
  329. package/dist/utils/parsing.d.ts.map +1 -0
  330. package/dist/utils/process.d.ts.map +1 -1
  331. package/dist/utils/process.js +90 -37
  332. package/dist/utils/templates.d.ts +2 -0
  333. package/dist/utils/templates.d.ts.map +1 -0
  334. package/dist/utils/templates.js +24 -0
  335. package/dist/utils/version.d.ts.map +1 -0
  336. package/dist/{version.js → utils/version.js} +1 -1
  337. package/package.json +35 -26
  338. package/dist/cli.d.ts +0 -23
  339. package/dist/cli.d.ts.map +0 -1
  340. package/dist/commands/add-directory/types.d.ts +0 -6
  341. package/dist/commands/add-directory/types.d.ts.map +0 -1
  342. package/dist/commands/add-directory/types.js +0 -1
  343. package/dist/commands/copy/types.d.ts +0 -3
  344. package/dist/commands/copy/types.d.ts.map +0 -1
  345. package/dist/commands/copy/types.js +0 -1
  346. package/dist/commands/exit/index.d.ts +0 -10
  347. package/dist/commands/exit/index.d.ts.map +0 -1
  348. package/dist/commands/exit/index.js +0 -21
  349. package/dist/commands/exit/types.d.ts +0 -8
  350. package/dist/commands/exit/types.d.ts.map +0 -1
  351. package/dist/commands/exit/types.js +0 -1
  352. package/dist/commands/exit/utils.d.ts +0 -2
  353. package/dist/commands/exit/utils.d.ts.map +0 -1
  354. package/dist/commands/exit/utils.js +0 -13
  355. package/dist/commands/prompt/index.d.ts +0 -5
  356. package/dist/commands/prompt/index.d.ts.map +0 -1
  357. package/dist/commands/prompt/index.js +0 -122
  358. package/dist/commands/prompt/types.d.ts +0 -15
  359. package/dist/commands/prompt/types.d.ts.map +0 -1
  360. package/dist/commands/prompt/types.js +0 -1
  361. package/dist/commands/prompt/utils.d.ts +0 -12
  362. package/dist/commands/prompt/utils.d.ts.map +0 -1
  363. package/dist/commands/prompt/utils.js +0 -107
  364. package/dist/commands/reset/index.d.ts +0 -3
  365. package/dist/commands/reset/index.d.ts.map +0 -1
  366. package/dist/commands/reset/index.js +0 -25
  367. package/dist/commands/reset/types.d.ts +0 -1
  368. package/dist/commands/reset/types.d.ts.map +0 -1
  369. package/dist/commands/reset/types.js +0 -3
  370. package/dist/commands/review/types.d.ts +0 -12
  371. package/dist/commands/review/types.d.ts.map +0 -1
  372. package/dist/commands/review/types.js +0 -1
  373. package/dist/commands/save/index.d.ts +0 -3
  374. package/dist/commands/save/index.d.ts.map +0 -1
  375. package/dist/commands/save/index.js +0 -19
  376. package/dist/config.d.ts.map +0 -1
  377. package/dist/dedent.d.ts.map +0 -1
  378. package/dist/formatting.d.ts.map +0 -1
  379. package/dist/logger.d.ts.map +0 -1
  380. package/dist/mentions.d.ts +0 -14
  381. package/dist/mentions.d.ts.map +0 -1
  382. package/dist/parsing.d.ts.map +0 -1
  383. package/dist/prompts.d.ts.map +0 -1
  384. package/dist/repl-new.d.ts +0 -65
  385. package/dist/repl-new.d.ts.map +0 -1
  386. package/dist/skills.d.ts +0 -16
  387. package/dist/skills.d.ts.map +0 -1
  388. package/dist/skills.js +0 -233
  389. package/dist/stdin.d.ts.map +0 -1
  390. package/dist/tui/autocomplete/path-provider.d.ts +0 -21
  391. package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
  392. package/dist/tui/autocomplete/path-provider.js +0 -164
  393. package/dist/utils/iterables.d.ts +0 -2
  394. package/dist/utils/iterables.d.ts.map +0 -1
  395. package/dist/utils/iterables.js +0 -6
  396. package/dist/version.d.ts.map +0 -1
  397. /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
  398. /package/dist/{dedent.js → utils/dedent.js} +0 -0
  399. /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
  400. /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
  401. /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
  402. /package/dist/{parsing.js → utils/parsing.js} +0 -0
  403. /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,107 @@ 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
+ * Checks if a character is valid inside braces (digits 0-9 or comma).
138
+ */
139
+ function isValidBraceChar(c) {
140
+ return (c >= "0" && c <= "9") || c === ",";
141
+ }
142
+ /**
143
+ * Checks if empty braces have a preceding atom.
144
+ * Empty braces with no preceding atom are treated as literal.
145
+ */
146
+ function hasPrecedingAtom(pattern, startIndex) {
147
+ if (startIndex === 0)
148
+ return false;
149
+ const prev = pattern[startIndex - 1];
150
+ return /\S/.test(prev);
151
+ }
152
+ /**
153
+ * Parses and validates the content inside braces {..}.
154
+ * Returns true if the brace content is invalid.
155
+ */
156
+ function isInvalidBraceContent(pattern, startIndex) {
157
+ const j = startIndex + 1;
158
+ // Find closing brace and check for invalid characters
159
+ let hasDigits = false;
160
+ let k = j;
161
+ while (k < pattern.length && pattern[k] !== "}") {
162
+ if (!isValidBraceChar(pattern[k])) {
163
+ return true;
164
+ }
165
+ if (pattern[k] >= "0" && pattern[k] <= "9") {
166
+ hasDigits = true;
167
+ }
168
+ k++;
169
+ }
170
+ // No closing brace found
171
+ if (k >= pattern.length) {
172
+ return true;
173
+ }
174
+ // Empty braces {} with preceding atom are invalid
175
+ if (!hasDigits && hasPrecedingAtom(pattern, startIndex)) {
176
+ return true;
177
+ }
178
+ return false;
179
+ }
180
+ /**
181
+ * Check for invalid repetition operators (e.g., {n}, {n,}, {n,m}) outside of character classes.
182
+ * Returns true if any invalid repetition operators are found.
183
+ */
184
+ function hasInvalidRepetition(pattern) {
185
+ for (let i = 0; i < pattern.length; i++) {
186
+ const ch = pattern[i];
187
+ if (ch === "\\") {
188
+ i++; // Skip the next character (escaped)
189
+ continue;
190
+ }
191
+ if (ch === "[") {
192
+ i = skipCharacterClass(pattern, i);
193
+ continue;
194
+ }
195
+ // Unmatched closing brace is invalid
196
+ if (ch === "}") {
197
+ return true;
198
+ }
199
+ // Handle opening brace
200
+ if (ch === "{") {
201
+ if (isInvalidBraceContent(pattern, i)) {
202
+ return true;
203
+ }
204
+ i = findClosingBrace(pattern, i);
205
+ }
206
+ }
207
+ return false;
208
+ }
209
+ /**
210
+ * Finds the index of the closing brace matching an opening brace.
211
+ * Returns the index of the closing brace, or the last index of pattern if not found.
212
+ */
213
+ function findClosingBrace(pattern, startIndex) {
214
+ let j = startIndex + 1;
215
+ while (j < pattern.length && pattern[j] !== "}") {
216
+ j++;
217
+ }
218
+ return j;
219
+ }
220
+ /**
221
+ * Count bracket/paren/brace pairs in a regex pattern, excluding character classes.
222
+ */
223
+ function countBrackets(pattern) {
127
224
  const counts = {
128
225
  openParen: 0,
129
226
  closeParen: 0,
@@ -175,91 +272,56 @@ export function likelyUnbalancedRegex(pattern) {
175
272
  }
176
273
  }
177
274
  }
275
+ return counts;
276
+ }
277
+ /**
278
+ * Check if a pattern contains regex metacharacters.
279
+ * Returns true if the pattern likely needs regex mode.
280
+ */
281
+ function containsRegexMetacharacters(pattern) {
282
+ // Regex metacharacters: ^ $ . * + ? [ ] ( ) { } | \
283
+ const metacharacterPattern = /[\^$.*+?[\](){}|\\]/;
284
+ return metacharacterPattern.test(pattern);
285
+ }
286
+ export function likelyUnbalancedRegex(pattern) {
287
+ // First check: unbalanced brackets/parentheses/braces = likely a typo, use literal
288
+ const counts = countBrackets(pattern);
178
289
  // Check for unbalanced brackets, parentheses, and braces
179
290
  const hasUnbalancedBrackets = counts.openBracket !== counts.closeBracket;
180
291
  const hasUnbalancedParens = counts.openParen !== counts.closeParen;
181
292
  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 ||
293
+ // Also check for invalid repetition operators
294
+ const hasInvalidRepetitionFlag = hasInvalidRepetition(pattern);
295
+ // If pattern has unbalanced syntax, treat as literal (user probably meant to type literal)
296
+ if (hasUnbalancedBrackets ||
243
297
  hasUnbalancedParens ||
244
298
  hasUnbalancedBraces ||
245
- hasInvalidRepetition);
299
+ hasInvalidRepetitionFlag) {
300
+ return true;
301
+ }
302
+ // Second check: if pattern has regex metacharacters, treat as regex (return false)
303
+ // This allows ripgrep to handle the pattern as a proper regex
304
+ if (containsRegexMetacharacters(pattern)) {
305
+ return false;
306
+ }
307
+ // Default: simple alphanumeric strings should use literal mode
308
+ return true;
246
309
  }
247
310
  /**
248
- * Search files for patterns using ripgrep
311
+ * Build grep command args array directly
249
312
  *
250
313
  * @param pattern - The regex pattern to search for
251
314
  * @param path - The path to search in
252
315
  * @param options - Additional options for the grep command
253
- * @returns The result of the grep command
316
+ * @returns The args array for the grep command
254
317
  */
255
- export function buildGrepCommand(pattern, path, options = {}) {
318
+ export function buildGrepArgs(pattern, path, options = {}) {
256
319
  const effectiveRecursive = options.recursive === null ? true : options.recursive;
257
320
  const effectiveIgnoreCase = options.ignoreCase === null ? false : options.ignoreCase;
258
321
  const effectiveSearchIgnored = options.searchIgnored === null ? false : options.searchIgnored;
259
322
  const effectiveFilePattern = options.filePattern;
260
323
  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.
324
+ // Use the pre-computed likelyUnbalanced result if available
263
325
  let effectiveLiteral;
264
326
  if (options.literal === true) {
265
327
  effectiveLiteral = true;
@@ -267,27 +329,30 @@ export function buildGrepCommand(pattern, path, options = {}) {
267
329
  else if (options.literal === false) {
268
330
  effectiveLiteral = false;
269
331
  }
332
+ else if (options.likelyUnbalanced !== undefined) {
333
+ effectiveLiteral = options.likelyUnbalanced;
334
+ }
270
335
  else {
271
336
  effectiveLiteral = likelyUnbalancedRegex(pattern);
272
337
  }
273
- let command = "rg --line-number";
338
+ const args = ["--json"];
274
339
  if (effectiveRecursive === false) {
275
- command += " --max-depth=0";
340
+ args.push("--max-depth=0");
276
341
  }
277
342
  if (effectiveIgnoreCase) {
278
- command += " --ignore-case";
343
+ args.push("--ignore-case");
279
344
  }
280
345
  if (effectiveContextLines !== null && effectiveContextLines !== undefined) {
281
- command += ` --context=${effectiveContextLines}`;
346
+ args.push(`--context=${effectiveContextLines}`);
282
347
  }
283
348
  if (effectiveFilePattern !== null && effectiveFilePattern !== undefined) {
284
- command += ` --glob=${JSON.stringify(effectiveFilePattern)}`;
349
+ args.push(`--glob=${effectiveFilePattern}`);
285
350
  }
286
351
  if (effectiveSearchIgnored) {
287
- command += " --no-ignore";
352
+ args.push("--no-ignore");
288
353
  }
289
354
  if (effectiveLiteral) {
290
- command += " -F";
355
+ args.push("-F");
291
356
  }
292
357
  // Use ripgrep's --max-count flag to limit matches per file for efficiency
293
358
  // This helps prevent any single file from dominating results
@@ -296,82 +361,88 @@ export function buildGrepCommand(pattern, path, options = {}) {
296
361
  options.maxResults > 0) {
297
362
  // Use a reasonable per-file limit (max 100 per file) to balance efficiency and completeness
298
363
  const perFileLimit = Math.min(options.maxResults, 100);
299
- command += ` --max-count=${perFileLimit}`;
364
+ args.push(`--max-count=${perFileLimit}`);
300
365
  }
301
- command += ` ${JSON.stringify(pattern)}`;
302
- command += ` ${JSON.stringify(path)}`;
303
- return command;
366
+ args.push(pattern);
367
+ args.push(path);
368
+ return args;
304
369
  }
305
370
  /**
306
- * Parse ripgrep output and extract structured match information
371
+ * Parse ripgrep JSON output and extract structured match information
307
372
  */
308
- export function parseRipgrepOutput(content) {
309
- if (content === "No matches found.") {
373
+ export function parseRipgrepJsonOutput(content) {
374
+ if (!content || content.trim() === "") {
310
375
  return [];
311
376
  }
312
- const lines = content.trim().split("\n");
313
377
  const parsed = [];
378
+ const lines = content.trim().split("\n");
314
379
  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
380
+ if (!line.trim()) {
321
381
  continue;
322
382
  }
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
- });
332
- continue;
333
- }
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;
383
+ try {
384
+ const rgResult = JSON.parse(line);
385
+ // Handle different ripgrep message types
386
+ if (rgResult.type === "match") {
387
+ const data = rgResult.data;
388
+ parsed.push({
389
+ file: data.path?.text ?? data.path?.bytes?.toString(),
390
+ line: data.line_number ?? 0,
391
+ content: data.lines?.text ?? data.line ?? "",
392
+ isMatch: true,
393
+ lineNumber: data.line_number,
394
+ absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
395
+ submatches: data.submatches?.map((sm) => ({
396
+ start: sm.start,
397
+ end: sm.end,
398
+ text: sm.text,
399
+ })),
400
+ });
401
+ }
402
+ else if (rgResult.type === "context") {
403
+ const data = rgResult.data;
404
+ parsed.push({
405
+ file: data.path?.text ?? data.path?.bytes?.toString(),
406
+ line: data.line_number ?? 0,
407
+ content: data.lines?.text ?? data.line ?? "",
408
+ isMatch: false,
409
+ isContext: true,
410
+ lineNumber: data.line_number,
411
+ absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
412
+ });
413
+ }
414
+ // Ignore other message types like "begin", "end", "summary"
343
415
  }
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;
416
+ catch { }
417
+ }
418
+ return parsed;
419
+ }
420
+ /**
421
+ * Convert parsed JSON matches back to legacy line-number format for backwards compatibility
422
+ */
423
+ function matchesToLegacyFormat(matches) {
424
+ const lines = [];
425
+ for (const match of matches) {
426
+ const lineNum = match.lineNumber ?? match.line;
427
+ const file = match.file ?? match.absolutePath;
428
+ if (file) {
429
+ if (match.isMatch) {
430
+ lines.push(`${file}:${lineNum}:${match.content}`);
431
+ }
432
+ else if (match.isContext) {
433
+ lines.push(`${file}-${lineNum}-${match.content}`);
434
+ }
355
435
  }
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;
436
+ else {
437
+ if (match.isMatch) {
438
+ lines.push(`${lineNum}:${match.content}`);
439
+ }
440
+ else if (match.isContext) {
441
+ lines.push(`${lineNum}-${match.content}`);
442
+ }
366
443
  }
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
444
  }
374
- return parsed;
445
+ return lines.join("\n");
375
446
  }
376
447
  /**
377
448
  * Count actual matches (excluding context lines)
@@ -385,81 +456,134 @@ export function countActualMatches(parsed) {
385
456
  export function countContextLines(parsed) {
386
457
  return parsed.filter((match) => match.isContext).length;
387
458
  }
459
+ function isContextNearKeptMatch(matches, index, indicesToKeep, contextWindow) {
460
+ for (let j = index - 1; j >= Math.max(0, index - contextWindow); j--) {
461
+ if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
462
+ return true;
463
+ }
464
+ }
465
+ for (let j = index + 1; j < Math.min(matches.length, index + contextWindow + 1); j++) {
466
+ if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
467
+ return true;
468
+ }
469
+ }
470
+ return false;
471
+ }
388
472
  /**
389
- * Truncate matches to a maximum number of results
473
+ * Truncate matches to a maximum number of results, preserving context lines for kept matches
390
474
  */
391
475
  export function truncateMatches(matches, maxResults) {
392
476
  if (!maxResults || maxResults <= 0) {
393
477
  return { truncated: matches, isTruncated: false };
394
478
  }
395
- const actualMatches = matches.filter((m) => m.isMatch && !m.isContext);
396
- if (actualMatches.length <= maxResults) {
479
+ // Find indices of actual matches (excluding context lines)
480
+ const matchIndices = [];
481
+ for (let i = 0; i < matches.length; i++) {
482
+ if (matches[i].isMatch && !matches[i].isContext) {
483
+ matchIndices.push(i);
484
+ }
485
+ }
486
+ if (matchIndices.length <= maxResults) {
397
487
  return { truncated: matches, isTruncated: false };
398
488
  }
489
+ // Get the indices of matches we want to keep
490
+ const indicesToKeep = new Set();
491
+ for (let i = 0; i < maxResults; i++) {
492
+ indicesToKeep.add(matchIndices[i]);
493
+ }
494
+ // Build truncated result: include all kept matches AND their associated context lines
399
495
  const truncated = [];
400
496
  let matchesKept = 0;
401
- for (const match of matches) {
497
+ const contextWindow = 3; // Include up to 3 context lines around each match
498
+ for (let i = 0; i < matches.length; i++) {
499
+ const match = matches[i];
402
500
  if (match.isMatch && !match.isContext) {
501
+ // This is an actual match
403
502
  if (matchesKept < maxResults) {
404
503
  truncated.push(match);
405
504
  matchesKept++;
406
505
  }
407
506
  else {
408
- break;
409
507
  }
410
508
  }
509
+ else if (match.isContext) {
510
+ if (isContextNearKeptMatch(matches, i, indicesToKeep, contextWindow)) {
511
+ truncated.push(match);
512
+ }
513
+ }
514
+ else {
515
+ // Other types, include them
516
+ truncated.push(match);
517
+ }
411
518
  }
412
519
  return {
413
520
  truncated,
414
521
  isTruncated: true,
415
522
  };
416
523
  }
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 = {}) {
524
+ export async function grepFilesStructured(pattern, path, options = {}, abortSignal) {
436
525
  try {
437
- const command = buildGrepCommand(pattern, path, options);
438
- const rawOutput = execSync(command, {
439
- encoding: "utf-8",
440
- stdio: ["pipe", "pipe", "pipe"],
526
+ const args = buildGrepArgs(pattern, path, options);
527
+ // Use execFile for async execution with proper abort signal handling
528
+ const rawOutput = await new Promise((resolve, reject) => {
529
+ const child = execFile("rg", args, {
530
+ encoding: "utf-8",
531
+ stdio: ["pipe", "pipe", "pipe"],
532
+ });
533
+ let stdout = "";
534
+ let stderr = "";
535
+ child.stdout?.on("data", (data) => {
536
+ stdout += data;
537
+ });
538
+ child.stderr?.on("data", (data) => {
539
+ stderr += data;
540
+ });
541
+ child.on("close", (code) => {
542
+ if (code === 0 || code === 1) {
543
+ resolve(stdout);
544
+ }
545
+ else {
546
+ reject(new Error(stderr || `ripgrep exited with code ${code}`));
547
+ }
548
+ });
549
+ child.on("error", (err) => {
550
+ reject(err);
551
+ });
552
+ if (abortSignal) {
553
+ abortSignal.addEventListener("abort", () => {
554
+ child.kill("SIGTERM");
555
+ reject(new Error("Grep search aborted"));
556
+ });
557
+ }
441
558
  });
442
- const parsedMatches = parseRipgrepOutput(rawOutput);
559
+ // Parse JSON output from ripgrep
560
+ const parsedMatches = parseRipgrepJsonOutput(rawOutput);
561
+ // If JSON parsing resulted in no matches, check if it's a "no matches" case
562
+ // (ripgrep --json returns empty for no matches)
563
+ const hasMatches = parsedMatches.length > 0;
443
564
  const matchCount = countActualMatches(parsedMatches);
444
565
  // Use the maxResults from options (which will be set by the execute function)
445
566
  const maxResults = options.maxResults;
446
567
  const { truncated, isTruncated } = truncateMatches(parsedMatches, maxResults);
447
568
  const displayedCount = countActualMatches(truncated);
448
569
  const displayedContextCount = countContextLines(truncated);
570
+ // Convert to legacy format for backwards compatibility
571
+ const legacyOutput = matchesToLegacyFormat(truncated);
572
+ const finalOutput = legacyOutput || "No matches found.";
449
573
  return {
450
- rawOutput,
574
+ rawOutput: finalOutput,
451
575
  parsedMatches: truncated,
452
576
  matchCount,
453
577
  displayedCount,
454
578
  contextCount: displayedContextCount,
455
- hasMatches: matchCount > 0,
579
+ hasMatches,
456
580
  isTruncated,
457
581
  };
458
582
  }
459
583
  catch (error) {
460
584
  const execError = error;
461
- const exitCode = execError?.status;
462
- if (exitCode === 1) {
585
+ const exitCode = execError.code;
586
+ if (exitCode === "1") {
463
587
  return {
464
588
  rawOutput: "No matches found.",
465
589
  parsedMatches: [],
@@ -468,10 +592,8 @@ export function grepFilesStructured(pattern, path, options = {}) {
468
592
  hasMatches: false,
469
593
  };
470
594
  }
471
- if (exitCode === 2) {
472
- const stderrStr = typeof execError.stderr === "string"
473
- ? execError.stderr
474
- : (execError.stderr?.toString("utf-8") ?? execError.message);
595
+ if (exitCode === "2") {
596
+ const stderrStr = execError.message;
475
597
  throw new Error(`Regex parse error in pattern "${pattern}": ${stderrStr}`);
476
598
  }
477
599
  throw new Error(`Error executing ripgrep: ${execError.message}`);