centaurus-cli 3.0.0 → 3.1.0

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 (552) hide show
  1. package/dist/ai/types.js +0 -1
  2. package/dist/ai/types.js.map +1 -1
  3. package/dist/cli-adapter.js +5047 -5037
  4. package/dist/cli-adapter.js.map +1 -1
  5. package/dist/commands/CommandParser.js +372 -315
  6. package/dist/commands/CommandParser.js.map +1 -1
  7. package/dist/config/build-config.js +11 -42
  8. package/dist/config/build-config.js.map +1 -1
  9. package/dist/config/defaultConfig.js +94 -82
  10. package/dist/config/defaultConfig.js.map +1 -1
  11. package/dist/config/manager.js +144 -160
  12. package/dist/config/manager.js.map +1 -1
  13. package/dist/config/mcp-config-manager.js +411 -364
  14. package/dist/config/mcp-config-manager.js.map +1 -1
  15. package/dist/config/models.js +118 -185
  16. package/dist/config/models.js.map +1 -1
  17. package/dist/config/slash-commands.js +186 -184
  18. package/dist/config/slash-commands.js.map +1 -1
  19. package/dist/config/types.js +33 -26
  20. package/dist/config/types.js.map +1 -1
  21. package/dist/context/command-detector.js +63 -67
  22. package/dist/context/command-detector.js.map +1 -1
  23. package/dist/context/context-manager.js +533 -518
  24. package/dist/context/context-manager.js.map +1 -1
  25. package/dist/context/handlers/docker-handler.js +518 -576
  26. package/dist/context/handlers/docker-handler.js.map +1 -1
  27. package/dist/context/handlers/ssh-handler.js +1050 -1109
  28. package/dist/context/handlers/ssh-handler.js.map +1 -1
  29. package/dist/context/handlers/wsl-handler.js +558 -630
  30. package/dist/context/handlers/wsl-handler.js.map +1 -1
  31. package/dist/context/index.js +42 -6
  32. package/dist/context/index.js.map +1 -1
  33. package/dist/context/subshell-handler.js +0 -4
  34. package/dist/context/subshell-handler.js.map +1 -1
  35. package/dist/context/types.js +20 -31
  36. package/dist/context/types.js.map +1 -1
  37. package/dist/hooks/useConnectivity.js +13 -10
  38. package/dist/hooks/useConnectivity.js.map +1 -1
  39. package/dist/hooks/useTerminalDimensions.js +67 -79
  40. package/dist/hooks/useTerminalDimensions.js.map +1 -1
  41. package/dist/index.js +228 -251
  42. package/dist/index.js.map +1 -1
  43. package/dist/mcp/mcp-command-handler.js +297 -260
  44. package/dist/mcp/mcp-command-handler.js.map +1 -1
  45. package/dist/mcp/mcp-server-manager.js +139 -155
  46. package/dist/mcp/mcp-server-manager.js.map +1 -1
  47. package/dist/mcp/mcp-tool-wrapper.js +74 -94
  48. package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
  49. package/dist/services/ai-autocomplete-agent.js +169 -181
  50. package/dist/services/ai-autocomplete-agent.js.map +1 -1
  51. package/dist/services/ai-context-injector.js +180 -93
  52. package/dist/services/ai-context-injector.js.map +1 -1
  53. package/dist/services/ai-service-client.js +513 -456
  54. package/dist/services/ai-service-client.js.map +1 -1
  55. package/dist/services/api-client.js +443 -441
  56. package/dist/services/api-client.js.map +1 -1
  57. package/dist/services/auth-handler.js +162 -198
  58. package/dist/services/auth-handler.js.map +1 -1
  59. package/dist/services/background-task-manager.js +258 -282
  60. package/dist/services/background-task-manager.js.map +1 -1
  61. package/dist/services/checkpoint-manager.js +1513 -973
  62. package/dist/services/checkpoint-manager.js.map +1 -1
  63. package/dist/services/clipboard-service.js +151 -200
  64. package/dist/services/clipboard-service.js.map +1 -1
  65. package/dist/services/connectivity-manager.js +63 -65
  66. package/dist/services/connectivity-manager.js.map +1 -1
  67. package/dist/services/conversation-manager.js +118 -121
  68. package/dist/services/conversation-manager.js.map +1 -1
  69. package/dist/services/environment-context-injector.js +160 -187
  70. package/dist/services/environment-context-injector.js.map +1 -1
  71. package/dist/services/fast-context-agent.js +203 -243
  72. package/dist/services/fast-context-agent.js.map +1 -1
  73. package/dist/services/input-detection-agent.js +190 -202
  74. package/dist/services/input-detection-agent.js.map +1 -1
  75. package/dist/services/input-requirement-detector.js +155 -189
  76. package/dist/services/input-requirement-detector.js.map +1 -1
  77. package/dist/services/local-chat-storage.js +342 -365
  78. package/dist/services/local-chat-storage.js.map +1 -1
  79. package/dist/services/monitored-shell-manager.js +225 -233
  80. package/dist/services/monitored-shell-manager.js.map +1 -1
  81. package/dist/services/ollama-service.js +293 -310
  82. package/dist/services/ollama-service.js.map +1 -1
  83. package/dist/services/rules-storage.js +142 -0
  84. package/dist/services/rules-storage.js.map +1 -0
  85. package/dist/services/session-quota-manager.js +219 -235
  86. package/dist/services/session-quota-manager.js.map +1 -1
  87. package/dist/services/shell-input-agent.js +299 -334
  88. package/dist/services/shell-input-agent.js.map +1 -1
  89. package/dist/services/sub-agent-manager.js +459 -501
  90. package/dist/services/sub-agent-manager.js.map +1 -1
  91. package/dist/services/warpify-detector.js +133 -183
  92. package/dist/services/warpify-detector.js.map +1 -1
  93. package/dist/services/workflow-storage.js +202 -217
  94. package/dist/services/workflow-storage.js.map +1 -1
  95. package/dist/test-ssh-handler.js +148 -193
  96. package/dist/test-ssh-handler.js.map +1 -1
  97. package/dist/tools/add-mcp.js +161 -0
  98. package/dist/tools/add-mcp.js.map +1 -0
  99. package/dist/tools/background-command.js +240 -273
  100. package/dist/tools/background-command.js.map +1 -1
  101. package/dist/tools/command.js +447 -440
  102. package/dist/tools/command.js.map +1 -1
  103. package/dist/tools/create-image.js +172 -202
  104. package/dist/tools/create-image.js.map +1 -1
  105. package/dist/tools/enter-remote-session.js +169 -215
  106. package/dist/tools/enter-remote-session.js.map +1 -1
  107. package/dist/tools/fast-context.js +60 -67
  108. package/dist/tools/fast-context.js.map +1 -1
  109. package/dist/tools/file-ops.js +605 -537
  110. package/dist/tools/file-ops.js.map +1 -1
  111. package/dist/tools/find-files.js +262 -303
  112. package/dist/tools/find-files.js.map +1 -1
  113. package/dist/tools/get-diff.js +423 -400
  114. package/dist/tools/get-diff.js.map +1 -1
  115. package/dist/tools/grep-search.js +966 -948
  116. package/dist/tools/grep-search.js.map +1 -1
  117. package/dist/tools/inspect-symbol.js +308 -323
  118. package/dist/tools/inspect-symbol.js.map +1 -1
  119. package/dist/tools/plan-mode.js +459 -503
  120. package/dist/tools/plan-mode.js.map +1 -1
  121. package/dist/tools/read-binary-file.js +160 -190
  122. package/dist/tools/read-binary-file.js.map +1 -1
  123. package/dist/tools/registry.js +100 -84
  124. package/dist/tools/registry.js.map +1 -1
  125. package/dist/tools/reproduce_issue.js +170 -151
  126. package/dist/tools/reproduce_issue.js.map +1 -1
  127. package/dist/tools/sub-agent.js +223 -228
  128. package/dist/tools/sub-agent.js.map +1 -1
  129. package/dist/tools/task-complete.js +28 -27
  130. package/dist/tools/task-complete.js.map +1 -1
  131. package/dist/tools/types.js +0 -1
  132. package/dist/tools/types.js.map +1 -1
  133. package/dist/tools/validation.js +96 -118
  134. package/dist/tools/validation.js.map +1 -1
  135. package/dist/tools/web-search.js +194 -194
  136. package/dist/tools/web-search.js.map +1 -1
  137. package/dist/tools/workflow-tool.js +77 -82
  138. package/dist/tools/workflow-tool.js.map +1 -1
  139. package/dist/types/index.js +0 -1
  140. package/dist/types/index.js.map +1 -1
  141. package/dist/types/rule.js +1 -0
  142. package/dist/types/rule.js.map +1 -0
  143. package/dist/types/workflow.js +0 -7
  144. package/dist/types/workflow.js.map +1 -1
  145. package/dist/ui/components/AgentTimer.js +24 -25
  146. package/dist/ui/components/AgentTimer.js.map +1 -1
  147. package/dist/ui/components/App.js +3266 -3249
  148. package/dist/ui/components/App.js.map +1 -1
  149. package/dist/ui/components/AuthScreen.js +22 -34
  150. package/dist/ui/components/AuthScreen.js.map +1 -1
  151. package/dist/ui/components/AuthWelcomeScreen.js +30 -24
  152. package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
  153. package/dist/ui/components/Breadcrumbs.js +53 -82
  154. package/dist/ui/components/Breadcrumbs.js.map +1 -1
  155. package/dist/ui/components/CircularSelectInput.js +59 -67
  156. package/dist/ui/components/CircularSelectInput.js.map +1 -1
  157. package/dist/ui/components/ClipboardFileAutocomplete.js +78 -39
  158. package/dist/ui/components/ClipboardFileAutocomplete.js.map +1 -1
  159. package/dist/ui/components/CodeBlock.js +24 -42
  160. package/dist/ui/components/CodeBlock.js.map +1 -1
  161. package/dist/ui/components/ConfigViewer.js +18 -25
  162. package/dist/ui/components/ConfigViewer.js.map +1 -1
  163. package/dist/ui/components/ConfirmPrompt.js +49 -71
  164. package/dist/ui/components/ConfirmPrompt.js.map +1 -1
  165. package/dist/ui/components/ConnectionStatusMessage.js +32 -83
  166. package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
  167. package/dist/ui/components/ContextWindowIndicator.js +34 -49
  168. package/dist/ui/components/ContextWindowIndicator.js.map +1 -1
  169. package/dist/ui/components/DetailedPlanReviewScreen.js +104 -106
  170. package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
  171. package/dist/ui/components/DiffViewer.js +68 -121
  172. package/dist/ui/components/DiffViewer.js.map +1 -1
  173. package/dist/ui/components/ErrorBoundary.js +40 -48
  174. package/dist/ui/components/ErrorBoundary.js.map +1 -1
  175. package/dist/ui/components/FileCreationPreview.js +29 -60
  176. package/dist/ui/components/FileCreationPreview.js.map +1 -1
  177. package/dist/ui/components/FileOperation.js +34 -29
  178. package/dist/ui/components/FileOperation.js.map +1 -1
  179. package/dist/ui/components/FileTagAutocomplete.js +55 -25
  180. package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
  181. package/dist/ui/components/FontRecommendation.js.map +1 -1
  182. package/dist/ui/components/GitDiffBreadcrumb.js +29 -0
  183. package/dist/ui/components/GitDiffBreadcrumb.js.map +1 -0
  184. package/dist/ui/components/InputBox.js +1620 -2150
  185. package/dist/ui/components/InputBox.js.map +1 -1
  186. package/dist/ui/components/InteractiveShell.js +234 -352
  187. package/dist/ui/components/InteractiveShell.js.map +1 -1
  188. package/dist/ui/components/KeyboardHelp.js +34 -35
  189. package/dist/ui/components/KeyboardHelp.js.map +1 -1
  190. package/dist/ui/components/LoadingIndicator.js +22 -25
  191. package/dist/ui/components/LoadingIndicator.js.map +1 -1
  192. package/dist/ui/components/MCPAddScreen.js +40 -51
  193. package/dist/ui/components/MCPAddScreen.js.map +1 -1
  194. package/dist/ui/components/MCPListScreen.js +40 -48
  195. package/dist/ui/components/MCPListScreen.js.map +1 -1
  196. package/dist/ui/components/MCPServerListScreen.js +49 -56
  197. package/dist/ui/components/MCPServerListScreen.js.map +1 -1
  198. package/dist/ui/components/MarkdownRenderer.js +69 -96
  199. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  200. package/dist/ui/components/MessageBox.js +66 -48
  201. package/dist/ui/components/MessageBox.js.map +1 -1
  202. package/dist/ui/components/MessageDisplay.js +150 -142
  203. package/dist/ui/components/MessageDisplay.js.map +1 -1
  204. package/dist/ui/components/MonitorModeAIPanel.js +46 -65
  205. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
  206. package/dist/ui/components/MultiLineInput.js +243 -277
  207. package/dist/ui/components/MultiLineInput.js.map +1 -1
  208. package/dist/ui/components/PasswordPrompt.js +37 -18
  209. package/dist/ui/components/PasswordPrompt.js.map +1 -1
  210. package/dist/ui/components/PlanAcceptedMessage.js +27 -38
  211. package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
  212. package/dist/ui/components/PlanReviewScreen.js +46 -50
  213. package/dist/ui/components/PlanReviewScreen.js.map +1 -1
  214. package/dist/ui/components/RulesEditorScreen.js +81 -0
  215. package/dist/ui/components/RulesEditorScreen.js.map +1 -0
  216. package/dist/ui/components/SelectPrompt.js +19 -8
  217. package/dist/ui/components/SelectPrompt.js.map +1 -1
  218. package/dist/ui/components/ShimmerText.js +44 -0
  219. package/dist/ui/components/ShimmerText.js.map +1 -0
  220. package/dist/ui/components/SlashCommandAutocomplete.js +49 -22
  221. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
  222. package/dist/ui/components/StatusBar.js +56 -87
  223. package/dist/ui/components/StatusBar.js.map +1 -1
  224. package/dist/ui/components/StreamingMessageDisplay.js +116 -99
  225. package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
  226. package/dist/ui/components/TaskCompletedMessage.js +28 -23
  227. package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
  228. package/dist/ui/components/TaskProgressIndicator.js +44 -70
  229. package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
  230. package/dist/ui/components/ThinkingDisplay.js +44 -41
  231. package/dist/ui/components/ThinkingDisplay.js.map +1 -1
  232. package/dist/ui/components/ToolExecutionMessage.js +772 -1326
  233. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  234. package/dist/ui/components/ToolExecutionStatus.js +53 -84
  235. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  236. package/dist/ui/components/ToolResult.js +22 -15
  237. package/dist/ui/components/ToolResult.js.map +1 -1
  238. package/dist/ui/components/VersionUpdatePrompt.js +88 -120
  239. package/dist/ui/components/VersionUpdatePrompt.js.map +1 -1
  240. package/dist/ui/components/WelcomeBanner.js +176 -26
  241. package/dist/ui/components/WelcomeBanner.js.map +1 -1
  242. package/dist/ui/components/WorkflowCreatorScreen.js +94 -161
  243. package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
  244. package/dist/utils/ansi-encoder.js +30 -61
  245. package/dist/utils/ansi-encoder.js.map +1 -1
  246. package/dist/utils/chat-formatter.js +327 -305
  247. package/dist/utils/chat-formatter.js.map +1 -1
  248. package/dist/utils/command-history.js +152 -174
  249. package/dist/utils/command-history.js.map +1 -1
  250. package/dist/utils/context-sanitizer.js +49 -112
  251. package/dist/utils/context-sanitizer.js.map +1 -1
  252. package/dist/utils/conversation-logger.js +292 -324
  253. package/dist/utils/conversation-logger.js.map +1 -1
  254. package/dist/utils/custom-commands-manager.js +126 -131
  255. package/dist/utils/custom-commands-manager.js.map +1 -1
  256. package/dist/utils/editor-utils.js +732 -837
  257. package/dist/utils/editor-utils.js.map +1 -1
  258. package/dist/utils/file.js +174 -213
  259. package/dist/utils/file.js.map +1 -1
  260. package/dist/utils/git-stats.js +169 -0
  261. package/dist/utils/git-stats.js.map +1 -0
  262. package/dist/utils/input-classifier.js +960 -482
  263. package/dist/utils/input-classifier.js.map +1 -1
  264. package/dist/utils/logger.js +48 -73
  265. package/dist/utils/logger.js.map +1 -1
  266. package/dist/utils/markdown-parser.js +277 -310
  267. package/dist/utils/markdown-parser.js.map +1 -1
  268. package/dist/utils/rule-reference-resolver.js +54 -0
  269. package/dist/utils/rule-reference-resolver.js.map +1 -0
  270. package/dist/utils/shell.js +144 -156
  271. package/dist/utils/shell.js.map +1 -1
  272. package/dist/utils/state.js +23 -22
  273. package/dist/utils/state.js.map +1 -1
  274. package/dist/utils/syntax-checker.js +279 -327
  275. package/dist/utils/syntax-checker.js.map +1 -1
  276. package/dist/utils/terminal-output.js +199 -302
  277. package/dist/utils/terminal-output.js.map +1 -1
  278. package/dist/utils/text-clipboard.js +47 -70
  279. package/dist/utils/text-clipboard.js.map +1 -1
  280. package/dist/utils/unicode-sanitizer.js +134 -197
  281. package/dist/utils/unicode-sanitizer.js.map +1 -1
  282. package/dist/utils/version-checker.js +46 -56
  283. package/dist/utils/version-checker.js.map +1 -1
  284. package/package.json +6 -4
  285. package/dist/ai/types.d.ts +0 -20
  286. package/dist/ai/types.d.ts.map +0 -1
  287. package/dist/cli-adapter.d.ts +0 -511
  288. package/dist/cli-adapter.d.ts.map +0 -1
  289. package/dist/commands/CommandParser.d.ts +0 -27
  290. package/dist/commands/CommandParser.d.ts.map +0 -1
  291. package/dist/config/build-config.d.ts +0 -42
  292. package/dist/config/build-config.d.ts.map +0 -1
  293. package/dist/config/defaultConfig.d.ts +0 -79
  294. package/dist/config/defaultConfig.d.ts.map +0 -1
  295. package/dist/config/manager.d.ts +0 -62
  296. package/dist/config/manager.d.ts.map +0 -1
  297. package/dist/config/mcp-config-manager.d.ts +0 -79
  298. package/dist/config/mcp-config-manager.d.ts.map +0 -1
  299. package/dist/config/models.d.ts +0 -83
  300. package/dist/config/models.d.ts.map +0 -1
  301. package/dist/config/slash-commands.d.ts +0 -23
  302. package/dist/config/slash-commands.d.ts.map +0 -1
  303. package/dist/config/types.d.ts +0 -35
  304. package/dist/config/types.d.ts.map +0 -1
  305. package/dist/context/command-detector.d.ts +0 -50
  306. package/dist/context/command-detector.d.ts.map +0 -1
  307. package/dist/context/context-manager.d.ts +0 -157
  308. package/dist/context/context-manager.d.ts.map +0 -1
  309. package/dist/context/handlers/docker-handler.d.ts +0 -130
  310. package/dist/context/handlers/docker-handler.d.ts.map +0 -1
  311. package/dist/context/handlers/ssh-handler.d.ts +0 -201
  312. package/dist/context/handlers/ssh-handler.d.ts.map +0 -1
  313. package/dist/context/handlers/wsl-handler.d.ts +0 -146
  314. package/dist/context/handlers/wsl-handler.d.ts.map +0 -1
  315. package/dist/context/index.d.ts +0 -8
  316. package/dist/context/index.d.ts.map +0 -1
  317. package/dist/context/subshell-handler.d.ts +0 -165
  318. package/dist/context/subshell-handler.d.ts.map +0 -1
  319. package/dist/context/types.d.ts +0 -70
  320. package/dist/context/types.d.ts.map +0 -1
  321. package/dist/hooks/useConnectivity.d.ts +0 -2
  322. package/dist/hooks/useConnectivity.d.ts.map +0 -1
  323. package/dist/hooks/useTerminalDimensions.d.ts +0 -41
  324. package/dist/hooks/useTerminalDimensions.d.ts.map +0 -1
  325. package/dist/index.d.ts +0 -9
  326. package/dist/index.d.ts.map +0 -1
  327. package/dist/mcp/mcp-command-handler.d.ts +0 -47
  328. package/dist/mcp/mcp-command-handler.d.ts.map +0 -1
  329. package/dist/mcp/mcp-server-manager.d.ts +0 -30
  330. package/dist/mcp/mcp-server-manager.d.ts.map +0 -1
  331. package/dist/mcp/mcp-tool-wrapper.d.ts +0 -12
  332. package/dist/mcp/mcp-tool-wrapper.d.ts.map +0 -1
  333. package/dist/services/ai-autocomplete-agent.d.ts +0 -39
  334. package/dist/services/ai-autocomplete-agent.d.ts.map +0 -1
  335. package/dist/services/ai-context-injector.d.ts +0 -41
  336. package/dist/services/ai-context-injector.d.ts.map +0 -1
  337. package/dist/services/ai-service-client.d.ts +0 -128
  338. package/dist/services/ai-service-client.d.ts.map +0 -1
  339. package/dist/services/api-client.d.ts +0 -353
  340. package/dist/services/api-client.d.ts.map +0 -1
  341. package/dist/services/auth-handler.d.ts +0 -30
  342. package/dist/services/auth-handler.d.ts.map +0 -1
  343. package/dist/services/background-task-manager.d.ts +0 -114
  344. package/dist/services/background-task-manager.d.ts.map +0 -1
  345. package/dist/services/checkpoint-manager.d.ts +0 -167
  346. package/dist/services/checkpoint-manager.d.ts.map +0 -1
  347. package/dist/services/clipboard-service.d.ts +0 -37
  348. package/dist/services/clipboard-service.d.ts.map +0 -1
  349. package/dist/services/connectivity-manager.d.ts +0 -18
  350. package/dist/services/connectivity-manager.d.ts.map +0 -1
  351. package/dist/services/conversation-manager.d.ts +0 -73
  352. package/dist/services/conversation-manager.d.ts.map +0 -1
  353. package/dist/services/environment-context-injector.d.ts +0 -69
  354. package/dist/services/environment-context-injector.d.ts.map +0 -1
  355. package/dist/services/fast-context-agent.d.ts +0 -12
  356. package/dist/services/fast-context-agent.d.ts.map +0 -1
  357. package/dist/services/input-detection-agent.d.ts +0 -40
  358. package/dist/services/input-detection-agent.d.ts.map +0 -1
  359. package/dist/services/input-requirement-detector.d.ts +0 -28
  360. package/dist/services/input-requirement-detector.d.ts.map +0 -1
  361. package/dist/services/local-chat-storage.d.ts +0 -182
  362. package/dist/services/local-chat-storage.d.ts.map +0 -1
  363. package/dist/services/monitored-shell-manager.d.ts +0 -120
  364. package/dist/services/monitored-shell-manager.d.ts.map +0 -1
  365. package/dist/services/ollama-service.d.ts +0 -197
  366. package/dist/services/ollama-service.d.ts.map +0 -1
  367. package/dist/services/session-quota-manager.d.ts +0 -101
  368. package/dist/services/session-quota-manager.d.ts.map +0 -1
  369. package/dist/services/shell-input-agent.d.ts +0 -89
  370. package/dist/services/shell-input-agent.d.ts.map +0 -1
  371. package/dist/services/sub-agent-manager.d.ts +0 -140
  372. package/dist/services/sub-agent-manager.d.ts.map +0 -1
  373. package/dist/services/warpify-detector.d.ts +0 -43
  374. package/dist/services/warpify-detector.d.ts.map +0 -1
  375. package/dist/services/workflow-storage.d.ts +0 -72
  376. package/dist/services/workflow-storage.d.ts.map +0 -1
  377. package/dist/test-ssh-handler.d.ts +0 -8
  378. package/dist/test-ssh-handler.d.ts.map +0 -1
  379. package/dist/tools/background-command.d.ts +0 -11
  380. package/dist/tools/background-command.d.ts.map +0 -1
  381. package/dist/tools/command.d.ts +0 -3
  382. package/dist/tools/command.d.ts.map +0 -1
  383. package/dist/tools/create-image.d.ts +0 -10
  384. package/dist/tools/create-image.d.ts.map +0 -1
  385. package/dist/tools/enter-remote-session.d.ts +0 -48
  386. package/dist/tools/enter-remote-session.d.ts.map +0 -1
  387. package/dist/tools/fast-context.d.ts +0 -3
  388. package/dist/tools/fast-context.d.ts.map +0 -1
  389. package/dist/tools/file-ops.d.ts +0 -7
  390. package/dist/tools/file-ops.d.ts.map +0 -1
  391. package/dist/tools/find-files.d.ts +0 -49
  392. package/dist/tools/find-files.d.ts.map +0 -1
  393. package/dist/tools/get-diff.d.ts +0 -14
  394. package/dist/tools/get-diff.d.ts.map +0 -1
  395. package/dist/tools/grep-search.d.ts +0 -155
  396. package/dist/tools/grep-search.d.ts.map +0 -1
  397. package/dist/tools/inspect-symbol.d.ts +0 -32
  398. package/dist/tools/inspect-symbol.d.ts.map +0 -1
  399. package/dist/tools/plan-mode.d.ts +0 -140
  400. package/dist/tools/plan-mode.d.ts.map +0 -1
  401. package/dist/tools/read-binary-file.d.ts +0 -10
  402. package/dist/tools/read-binary-file.d.ts.map +0 -1
  403. package/dist/tools/registry.d.ts +0 -31
  404. package/dist/tools/registry.d.ts.map +0 -1
  405. package/dist/tools/reproduce_issue.d.ts +0 -2
  406. package/dist/tools/reproduce_issue.d.ts.map +0 -1
  407. package/dist/tools/sub-agent.d.ts +0 -9
  408. package/dist/tools/sub-agent.d.ts.map +0 -1
  409. package/dist/tools/task-complete.d.ts +0 -3
  410. package/dist/tools/task-complete.d.ts.map +0 -1
  411. package/dist/tools/types.d.ts +0 -40
  412. package/dist/tools/types.d.ts.map +0 -1
  413. package/dist/tools/validation.d.ts +0 -47
  414. package/dist/tools/validation.d.ts.map +0 -1
  415. package/dist/tools/web-search.d.ts +0 -24
  416. package/dist/tools/web-search.d.ts.map +0 -1
  417. package/dist/tools/workflow-tool.d.ts +0 -11
  418. package/dist/tools/workflow-tool.d.ts.map +0 -1
  419. package/dist/types/index.d.ts +0 -123
  420. package/dist/types/index.d.ts.map +0 -1
  421. package/dist/types/workflow.d.ts +0 -110
  422. package/dist/types/workflow.d.ts.map +0 -1
  423. package/dist/ui/components/AgentTimer.d.ts +0 -7
  424. package/dist/ui/components/AgentTimer.d.ts.map +0 -1
  425. package/dist/ui/components/App.d.ts +0 -197
  426. package/dist/ui/components/App.d.ts.map +0 -1
  427. package/dist/ui/components/AuthScreen.d.ts +0 -8
  428. package/dist/ui/components/AuthScreen.d.ts.map +0 -1
  429. package/dist/ui/components/AuthWelcomeScreen.d.ts +0 -8
  430. package/dist/ui/components/AuthWelcomeScreen.d.ts.map +0 -1
  431. package/dist/ui/components/Breadcrumbs.d.ts +0 -13
  432. package/dist/ui/components/Breadcrumbs.d.ts.map +0 -1
  433. package/dist/ui/components/CircularSelectInput.d.ts +0 -24
  434. package/dist/ui/components/CircularSelectInput.d.ts.map +0 -1
  435. package/dist/ui/components/ClipboardFileAutocomplete.d.ts +0 -10
  436. package/dist/ui/components/ClipboardFileAutocomplete.d.ts.map +0 -1
  437. package/dist/ui/components/CodeBlock.d.ts +0 -9
  438. package/dist/ui/components/CodeBlock.d.ts.map +0 -1
  439. package/dist/ui/components/ConfigViewer.d.ts +0 -11
  440. package/dist/ui/components/ConfigViewer.d.ts.map +0 -1
  441. package/dist/ui/components/ConfirmPrompt.d.ts +0 -13
  442. package/dist/ui/components/ConfirmPrompt.d.ts.map +0 -1
  443. package/dist/ui/components/ConnectionStatusMessage.d.ts +0 -17
  444. package/dist/ui/components/ConnectionStatusMessage.d.ts.map +0 -1
  445. package/dist/ui/components/ContextWindowIndicator.d.ts +0 -8
  446. package/dist/ui/components/ContextWindowIndicator.d.ts.map +0 -1
  447. package/dist/ui/components/DetailedPlanReviewScreen.d.ts +0 -17
  448. package/dist/ui/components/DetailedPlanReviewScreen.d.ts.map +0 -1
  449. package/dist/ui/components/DiffViewer.d.ts +0 -9
  450. package/dist/ui/components/DiffViewer.d.ts.map +0 -1
  451. package/dist/ui/components/ErrorBoundary.d.ts +0 -17
  452. package/dist/ui/components/ErrorBoundary.d.ts.map +0 -1
  453. package/dist/ui/components/FileCreationPreview.d.ts +0 -8
  454. package/dist/ui/components/FileCreationPreview.d.ts.map +0 -1
  455. package/dist/ui/components/FileOperation.d.ts +0 -10
  456. package/dist/ui/components/FileOperation.d.ts.map +0 -1
  457. package/dist/ui/components/FileTagAutocomplete.d.ts +0 -11
  458. package/dist/ui/components/FileTagAutocomplete.d.ts.map +0 -1
  459. package/dist/ui/components/FontRecommendation.d.ts +0 -1
  460. package/dist/ui/components/FontRecommendation.d.ts.map +0 -1
  461. package/dist/ui/components/InputBox.d.ts +0 -42
  462. package/dist/ui/components/InputBox.d.ts.map +0 -1
  463. package/dist/ui/components/InteractiveShell.d.ts +0 -30
  464. package/dist/ui/components/InteractiveShell.d.ts.map +0 -1
  465. package/dist/ui/components/KeyboardHelp.d.ts +0 -7
  466. package/dist/ui/components/KeyboardHelp.d.ts.map +0 -1
  467. package/dist/ui/components/LoadingIndicator.d.ts +0 -3
  468. package/dist/ui/components/LoadingIndicator.d.ts.map +0 -1
  469. package/dist/ui/components/MCPAddScreen.d.ts +0 -13
  470. package/dist/ui/components/MCPAddScreen.d.ts.map +0 -1
  471. package/dist/ui/components/MCPListScreen.d.ts +0 -17
  472. package/dist/ui/components/MCPListScreen.d.ts.map +0 -1
  473. package/dist/ui/components/MCPServerListScreen.d.ts +0 -16
  474. package/dist/ui/components/MCPServerListScreen.d.ts.map +0 -1
  475. package/dist/ui/components/MarkdownRenderer.d.ts +0 -8
  476. package/dist/ui/components/MarkdownRenderer.d.ts.map +0 -1
  477. package/dist/ui/components/MessageBox.d.ts +0 -10
  478. package/dist/ui/components/MessageBox.d.ts.map +0 -1
  479. package/dist/ui/components/MessageDisplay.d.ts +0 -14
  480. package/dist/ui/components/MessageDisplay.d.ts.map +0 -1
  481. package/dist/ui/components/MonitorModeAIPanel.d.ts +0 -23
  482. package/dist/ui/components/MonitorModeAIPanel.d.ts.map +0 -1
  483. package/dist/ui/components/MultiLineInput.d.ts +0 -13
  484. package/dist/ui/components/MultiLineInput.d.ts.map +0 -1
  485. package/dist/ui/components/PasswordPrompt.d.ts +0 -9
  486. package/dist/ui/components/PasswordPrompt.d.ts.map +0 -1
  487. package/dist/ui/components/PlanAcceptedMessage.d.ts +0 -20
  488. package/dist/ui/components/PlanAcceptedMessage.d.ts.map +0 -1
  489. package/dist/ui/components/PlanReviewScreen.d.ts +0 -14
  490. package/dist/ui/components/PlanReviewScreen.d.ts.map +0 -1
  491. package/dist/ui/components/SelectPrompt.d.ts +0 -12
  492. package/dist/ui/components/SelectPrompt.d.ts.map +0 -1
  493. package/dist/ui/components/SlashCommandAutocomplete.d.ts +0 -13
  494. package/dist/ui/components/SlashCommandAutocomplete.d.ts.map +0 -1
  495. package/dist/ui/components/StatusBar.d.ts +0 -14
  496. package/dist/ui/components/StatusBar.d.ts.map +0 -1
  497. package/dist/ui/components/StreamingMessageDisplay.d.ts +0 -15
  498. package/dist/ui/components/StreamingMessageDisplay.d.ts.map +0 -1
  499. package/dist/ui/components/TaskCompletedMessage.d.ts +0 -14
  500. package/dist/ui/components/TaskCompletedMessage.d.ts.map +0 -1
  501. package/dist/ui/components/TaskProgressIndicator.d.ts +0 -18
  502. package/dist/ui/components/TaskProgressIndicator.d.ts.map +0 -1
  503. package/dist/ui/components/ThinkingDisplay.d.ts +0 -15
  504. package/dist/ui/components/ThinkingDisplay.d.ts.map +0 -1
  505. package/dist/ui/components/ToolExecutionMessage.d.ts +0 -8
  506. package/dist/ui/components/ToolExecutionMessage.d.ts.map +0 -1
  507. package/dist/ui/components/ToolExecutionStatus.d.ts +0 -10
  508. package/dist/ui/components/ToolExecutionStatus.d.ts.map +0 -1
  509. package/dist/ui/components/ToolResult.d.ts +0 -10
  510. package/dist/ui/components/ToolResult.d.ts.map +0 -1
  511. package/dist/ui/components/VersionUpdatePrompt.d.ts +0 -9
  512. package/dist/ui/components/VersionUpdatePrompt.d.ts.map +0 -1
  513. package/dist/ui/components/WelcomeBanner.d.ts +0 -3
  514. package/dist/ui/components/WelcomeBanner.d.ts.map +0 -1
  515. package/dist/ui/components/WorkflowCreatorScreen.d.ts +0 -25
  516. package/dist/ui/components/WorkflowCreatorScreen.d.ts.map +0 -1
  517. package/dist/utils/ansi-encoder.d.ts +0 -7
  518. package/dist/utils/ansi-encoder.d.ts.map +0 -1
  519. package/dist/utils/chat-formatter.d.ts +0 -12
  520. package/dist/utils/chat-formatter.d.ts.map +0 -1
  521. package/dist/utils/command-history.d.ts +0 -24
  522. package/dist/utils/command-history.d.ts.map +0 -1
  523. package/dist/utils/context-sanitizer.d.ts +0 -50
  524. package/dist/utils/context-sanitizer.d.ts.map +0 -1
  525. package/dist/utils/conversation-logger.d.ts +0 -142
  526. package/dist/utils/conversation-logger.d.ts.map +0 -1
  527. package/dist/utils/custom-commands-manager.d.ts +0 -59
  528. package/dist/utils/custom-commands-manager.d.ts.map +0 -1
  529. package/dist/utils/editor-utils.d.ts +0 -101
  530. package/dist/utils/editor-utils.d.ts.map +0 -1
  531. package/dist/utils/file.d.ts +0 -61
  532. package/dist/utils/file.d.ts.map +0 -1
  533. package/dist/utils/input-classifier.d.ts +0 -25
  534. package/dist/utils/input-classifier.d.ts.map +0 -1
  535. package/dist/utils/logger.d.ts +0 -17
  536. package/dist/utils/logger.d.ts.map +0 -1
  537. package/dist/utils/markdown-parser.d.ts +0 -60
  538. package/dist/utils/markdown-parser.d.ts.map +0 -1
  539. package/dist/utils/shell.d.ts +0 -47
  540. package/dist/utils/shell.d.ts.map +0 -1
  541. package/dist/utils/state.d.ts +0 -13
  542. package/dist/utils/state.d.ts.map +0 -1
  543. package/dist/utils/syntax-checker.d.ts +0 -24
  544. package/dist/utils/syntax-checker.d.ts.map +0 -1
  545. package/dist/utils/terminal-output.d.ts +0 -25
  546. package/dist/utils/terminal-output.d.ts.map +0 -1
  547. package/dist/utils/text-clipboard.d.ts +0 -12
  548. package/dist/utils/text-clipboard.d.ts.map +0 -1
  549. package/dist/utils/unicode-sanitizer.d.ts +0 -44
  550. package/dist/utils/unicode-sanitizer.d.ts.map +0 -1
  551. package/dist/utils/version-checker.d.ts +0 -14
  552. package/dist/utils/version-checker.d.ts.map +0 -1
@@ -1,1005 +1,1023 @@
1
- import { spawn } from 'child_process';
2
- import { execFile } from 'child_process';
3
- import { promisify } from 'util';
4
- import * as path from 'path';
5
- import * as fs from 'fs';
6
- import * as readline from 'readline';
1
+ import { spawn } from "child_process";
2
+ import { execFile } from "child_process";
3
+ import { promisify } from "util";
4
+ import * as path from "path";
5
+ import * as fs from "fs";
6
+ import * as readline from "readline";
7
7
  const execFileAsync = promisify(execFile);
8
- // ============================================================================
9
- // GREP SEARCH TOOL CLASS - CROSS-PLATFORM ROBUST IMPLEMENTATION
10
- // ============================================================================
11
- /**
12
- * GrepSearchTool - Cross-platform text search with multiple backends
13
- *
14
- * Backend priority:
15
- * 1. ripgrep (fastest, cross-platform)
16
- * 2. grep (Linux/macOS native)
17
- * 3. PowerShell Select-String (Windows, good Unicode support)
18
- * 4. Pure Node.js (universal fallback, no dependencies)
19
- *
20
- * Features:
21
- * - Streaming output to handle large repos
22
- * - Early abort when MAX_MATCHES reached
23
- * - Proper encoding handling (UTF-8 with fallback)
24
- * - CRLF normalization
25
- * - Windows path support (drive letters, UNC)
26
- */
27
- export class GrepSearchTool {
28
- static DEFAULT_MAX_MATCHES = 50;
29
- static MAX_LINE_LENGTH = 300;
30
- static CONTEXT_LINES = 2;
31
- static SEARCH_TIMEOUT = 30000;
32
- // Cache tool availability to avoid repeated checks
33
- static toolCache = new Map();
34
- /**
35
- * Check if a command is available
36
- */
37
- async hasCommand(cmd, args = ['--version']) {
38
- const cacheKey = `${cmd}:${args.join(',')}`;
39
- if (GrepSearchTool.toolCache.has(cacheKey)) {
40
- return GrepSearchTool.toolCache.get(cacheKey);
41
- }
42
- try {
43
- await Promise.race([
44
- execFileAsync(cmd, args, { timeout: 2000 }),
45
- new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000))
46
- ]);
47
- GrepSearchTool.toolCache.set(cacheKey, true);
48
- return true;
49
- }
50
- catch {
51
- GrepSearchTool.toolCache.set(cacheKey, false);
52
- return false;
53
- }
8
+ function hasUnescapedChar(value, targetChar) {
9
+ let escaped = false;
10
+ for (const ch of value) {
11
+ if (escaped) {
12
+ escaped = false;
13
+ continue;
54
14
  }
55
- async hasRipgrep() {
56
- return this.hasCommand('rg', ['--version']);
15
+ if (ch === "\\") {
16
+ escaped = true;
17
+ continue;
57
18
  }
58
- async hasGrep() {
59
- return this.hasCommand('grep', ['--version']);
19
+ if (ch === targetChar) {
20
+ return true;
60
21
  }
61
- async hasPowerShell() {
62
- // Try pwsh first (PowerShell Core), then powershell (Windows PowerShell)
63
- if (await this.hasCommand('pwsh', ['-Version']))
64
- return true;
65
- if (process.platform === 'win32') {
66
- return this.hasCommand('powershell', ['-Version']);
67
- }
68
- return false;
22
+ }
23
+ return false;
24
+ }
25
+ function looksLikeRegexPattern(value) {
26
+ const query = value.trim();
27
+ if (!query) {
28
+ return false;
29
+ }
30
+ if (hasUnescapedChar(query, "|")) {
31
+ return true;
32
+ }
33
+ if (/\\[dDsSwWbBAZzGQEnrtvf0(){}[\].+*?^$|\\]/.test(query)) {
34
+ return true;
35
+ }
36
+ if (query.startsWith("^") || /(^|[^\\])\$$/.test(query)) {
37
+ return true;
38
+ }
39
+ if (/(^|[^\\])\[[^\]]+\]/.test(query)) {
40
+ return true;
41
+ }
42
+ if (/(^|[^\\])\((\?:|\?=|\?!|\?<=|\?<!)/.test(query)) {
43
+ return true;
44
+ }
45
+ if (/(^|[^\\])\{[0-9]+(,[0-9]*)?\}/.test(query)) {
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ function usesRegexFeaturesUnsupportedByGrep(value) {
51
+ const query = value.trim();
52
+ if (!query) {
53
+ return false;
54
+ }
55
+ if (/\\[bBkK]/.test(query)) {
56
+ return true;
57
+ }
58
+ if (/\\[pPkK]\{/.test(query)) {
59
+ return true;
60
+ }
61
+ if (/\(\?(?::|=|!|<=|<!|<[^>]+>)/.test(query)) {
62
+ return true;
63
+ }
64
+ if (/(?:\*\?|\+\?|\?\?|\{\d+(?:,\d*)?\}\?)/.test(query)) {
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+ function normalizeRegexForGrep(pattern) {
70
+ let out = "";
71
+ for (let i = 0; i < pattern.length; i++) {
72
+ const ch = pattern[i];
73
+ if (ch !== "\\") {
74
+ out += ch;
75
+ continue;
69
76
  }
70
- /**
71
- * Get the PowerShell executable name
72
- */
73
- async getPowerShellExe() {
74
- if (await this.hasCommand('pwsh', ['-Version']))
75
- return 'pwsh';
76
- return 'powershell';
77
+ if (i + 1 >= pattern.length) {
78
+ out += "\\";
79
+ break;
77
80
  }
78
- /**
79
- * Normalize file path to forward slashes
80
- */
81
- normalizePath(filePath) {
82
- return filePath.replace(/\\/g, '/');
81
+ const next = pattern[i + 1];
82
+ switch (next) {
83
+ case "d":
84
+ out += "[0-9]";
85
+ break;
86
+ case "D":
87
+ out += "[^0-9]";
88
+ break;
89
+ case "s":
90
+ out += "[[:space:]]";
91
+ break;
92
+ case "S":
93
+ out += "[^[:space:]]";
94
+ break;
95
+ case "w":
96
+ out += "[[:alnum:]_]";
97
+ break;
98
+ case "W":
99
+ out += "[^[:alnum:]_]";
100
+ break;
101
+ default:
102
+ out += `\\${next}`;
103
+ break;
83
104
  }
84
- /**
85
- * Truncate long lines
86
- */
87
- truncateLine(line) {
88
- if (line.length <= GrepSearchTool.MAX_LINE_LENGTH) {
89
- return line;
90
- }
91
- return line.substring(0, GrepSearchTool.MAX_LINE_LENGTH) + ' [truncated]';
105
+ i++;
106
+ }
107
+ return out;
108
+ }
109
+ function escapePowerShellSingleQuoted(value) {
110
+ return value.replace(/'/g, "''");
111
+ }
112
+ class GrepSearchTool {
113
+ static DEFAULT_MAX_MATCHES = 50;
114
+ static MAX_LINE_LENGTH = 300;
115
+ static CONTEXT_LINES = 2;
116
+ static SEARCH_TIMEOUT = 3e4;
117
+ // Cache tool availability to avoid repeated checks
118
+ static toolCache = /* @__PURE__ */ new Map();
119
+ /**
120
+ * Check if a command is available
121
+ */
122
+ async hasCommand(cmd, args = ["--version"]) {
123
+ const cacheKey = `${cmd}:${args.join(",")}`;
124
+ if (GrepSearchTool.toolCache.has(cacheKey)) {
125
+ return GrepSearchTool.toolCache.get(cacheKey);
92
126
  }
93
- /**
94
- * Normalize line endings (CRLF -> LF)
95
- */
96
- normalizeLineEndings(text) {
97
- return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
127
+ try {
128
+ await Promise.race([
129
+ execFileAsync(cmd, args, { timeout: 2e3 }),
130
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 2e3))
131
+ ]);
132
+ GrepSearchTool.toolCache.set(cacheKey, true);
133
+ return true;
134
+ } catch {
135
+ GrepSearchTool.toolCache.set(cacheKey, false);
136
+ return false;
98
137
  }
99
- /**
100
- * Parse file:line:content format robustly, handling Windows paths
101
- * E.g., "C:\foo\bar.ts:42:content" or "/foo/bar.ts:42:content"
102
- */
103
- parseGrepLine(line) {
104
- // Windows absolute path: C:\path\to\file:line:content
105
- const m1 = line.match(/^([A-Za-z]:\\.+?):(\d+):(.*)$/);
106
- if (m1)
107
- return { file: m1[1], lineNum: +m1[2], content: m1[3] };
108
- // UNC path: \\server\share\path:line:content
109
- const m2 = line.match(/^(\\\\.+?):(\d+):(.*)$/);
110
- if (m2)
111
- return { file: m2[1], lineNum: +m2[2], content: m2[3] };
112
- // Unix / relative path: /path/to/file:line:content or path/to/file:line:content
113
- const m3 = line.match(/^(.+?):(\d+):(.*)$/);
114
- if (m3)
115
- return { file: m3[1], lineNum: +m3[2], content: m3[3] };
116
- return null;
138
+ }
139
+ async hasRipgrep() {
140
+ return this.hasCommand("rg", ["--version"]);
141
+ }
142
+ async hasGrep() {
143
+ return this.hasCommand("grep", ["--version"]);
144
+ }
145
+ async hasPowerShell() {
146
+ if (await this.hasCommand("pwsh", ["-Version"])) return true;
147
+ if (process.platform === "win32") {
148
+ return this.hasCommand("powershell", ["-Version"]);
117
149
  }
118
- /**
119
- * Create regex from pattern
120
- */
121
- createSearchRegex(pattern, isRegex, caseInsensitive) {
122
- const flags = caseInsensitive ? 'gi' : 'g';
123
- if (isRegex) {
124
- try {
125
- return new RegExp(pattern, flags);
126
- }
127
- catch {
128
- // Invalid regex, escape and use as literal
129
- return new RegExp(this.escapeRegex(pattern), flags);
130
- }
131
- }
150
+ return false;
151
+ }
152
+ /**
153
+ * Get the PowerShell executable name
154
+ */
155
+ async getPowerShellExe() {
156
+ if (await this.hasCommand("pwsh", ["-Version"])) return "pwsh";
157
+ return "powershell";
158
+ }
159
+ /**
160
+ * Normalize file path to forward slashes
161
+ */
162
+ normalizePath(filePath) {
163
+ return filePath.replace(/\\/g, "/");
164
+ }
165
+ /**
166
+ * Truncate long lines
167
+ */
168
+ truncateLine(line) {
169
+ if (line.length <= GrepSearchTool.MAX_LINE_LENGTH) {
170
+ return line;
171
+ }
172
+ return line.substring(0, GrepSearchTool.MAX_LINE_LENGTH) + " [truncated]";
173
+ }
174
+ /**
175
+ * Normalize line endings (CRLF -> LF)
176
+ */
177
+ normalizeLineEndings(text) {
178
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
179
+ }
180
+ /**
181
+ * Parse file:line:content format robustly, handling Windows paths
182
+ * E.g., "C:\foo\bar.ts:42:content" or "/foo/bar.ts:42:content"
183
+ */
184
+ parseGrepLine(line) {
185
+ const m1 = line.match(/^([A-Za-z]:\\.+?):(\d+):(.*)$/);
186
+ if (m1) return { file: m1[1], lineNum: +m1[2], content: m1[3] };
187
+ const m2 = line.match(/^(\\\\.+?):(\d+):(.*)$/);
188
+ if (m2) return { file: m2[1], lineNum: +m2[2], content: m2[3] };
189
+ const m3 = line.match(/^(.+?):(\d+):(.*)$/);
190
+ if (m3) return { file: m3[1], lineNum: +m3[2], content: m3[3] };
191
+ return null;
192
+ }
193
+ /**
194
+ * Create regex from pattern
195
+ */
196
+ createSearchRegex(pattern, isRegex, caseInsensitive) {
197
+ const flags = caseInsensitive ? "gi" : "g";
198
+ if (isRegex) {
199
+ try {
200
+ return new RegExp(pattern, flags);
201
+ } catch {
132
202
  return new RegExp(this.escapeRegex(pattern), flags);
203
+ }
133
204
  }
134
- /**
135
- * Escape regex special characters
136
- */
137
- escapeRegex(str) {
138
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
205
+ return new RegExp(this.escapeRegex(pattern), flags);
206
+ }
207
+ /**
208
+ * Escape regex special characters
209
+ */
210
+ escapeRegex(str) {
211
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
212
+ }
213
+ /**
214
+ * Check if file matches include patterns
215
+ */
216
+ matchesIncludesFilter(filePath, includes) {
217
+ if (!includes || includes.length === 0) {
218
+ return true;
139
219
  }
140
- /**
141
- * Check if file matches include patterns
142
- */
143
- matchesIncludesFilter(filePath, includes) {
144
- if (!includes || includes.length === 0) {
145
- return true;
220
+ const fileName = path.basename(filePath);
221
+ const normalizedPath = this.normalizePath(filePath);
222
+ for (const pattern of includes) {
223
+ if (pattern.startsWith("*.")) {
224
+ const ext = pattern.substring(1);
225
+ if (fileName.endsWith(ext)) {
226
+ return true;
146
227
  }
147
- const fileName = path.basename(filePath);
148
- const normalizedPath = this.normalizePath(filePath);
149
- for (const pattern of includes) {
150
- if (pattern.startsWith('*.')) {
151
- const ext = pattern.substring(1);
152
- if (fileName.endsWith(ext)) {
153
- return true;
154
- }
155
- }
156
- else if (pattern.includes('**')) {
157
- const ext = pattern.replace('**/', '').replace('*', '');
158
- if (fileName.endsWith(ext) || normalizedPath.includes(ext)) {
159
- return true;
160
- }
161
- }
162
- else if (fileName === pattern || normalizedPath.includes(pattern)) {
163
- return true;
164
- }
228
+ } else if (pattern.includes("**")) {
229
+ const ext = pattern.replace("**/", "").replace("*", "");
230
+ if (fileName.endsWith(ext) || normalizedPath.includes(ext)) {
231
+ return true;
165
232
  }
166
- return false;
233
+ } else if (fileName === pattern || normalizedPath.includes(pattern)) {
234
+ return true;
235
+ }
167
236
  }
168
- /**
169
- * Check if file is likely binary
170
- */
171
- async isBinaryFile(filePath) {
172
- try {
173
- const fd = await fs.promises.open(filePath, 'r');
174
- const buffer = Buffer.alloc(8192);
175
- const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
176
- await fd.close();
177
- // Check for null bytes (common in binary files)
178
- for (let i = 0; i < bytesRead; i++) {
179
- if (buffer[i] === 0)
180
- return true;
181
- }
182
- return false;
183
- }
184
- catch {
185
- return true; // Assume binary on error
186
- }
237
+ return false;
238
+ }
239
+ /**
240
+ * Check if file is likely binary
241
+ */
242
+ async isBinaryFile(filePath) {
243
+ try {
244
+ const fd = await fs.promises.open(filePath, "r");
245
+ const buffer = Buffer.alloc(8192);
246
+ const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
247
+ await fd.close();
248
+ for (let i = 0; i < bytesRead; i++) {
249
+ if (buffer[i] === 0) return true;
250
+ }
251
+ return false;
252
+ } catch {
253
+ return true;
187
254
  }
188
- // ========================================================================
189
- // RIPGREP BACKEND (Streaming)
190
- // ========================================================================
191
- async searchWithRipgrepStreaming(params, cwd) {
192
- return new Promise((resolve, reject) => {
193
- const args = [
194
- '--json',
195
- `--context=${GrepSearchTool.CONTEXT_LINES}`,
196
- ];
197
- if (params.CaseInsensitive) {
198
- args.push('--ignore-case');
199
- }
200
- if (!params.IsRegex) {
201
- args.push('--fixed-strings');
202
- }
203
- if (params.Includes && params.Includes.length > 0) {
204
- params.Includes.forEach(pattern => {
205
- args.push('--glob', pattern);
206
- });
207
- }
208
- args.push('--', params.Query, params.SearchPath);
209
- const matches = [];
210
- let truncated = false;
211
- let currentFile = null;
212
- let contextQueue = [];
213
- let pendingMatch = null;
214
- let contextAfterCount = 0;
215
- // RACECONDITION FIX: Guard against double-resolution
216
- let finished = false;
217
- const child = spawn('rg', args, {
218
- cwd,
219
- env: { ...process.env, LANG: 'en_US.UTF-8' }
220
- });
221
- const rl = readline.createInterface({ input: child.stdout });
222
- // Robust cleanup function
223
- const done = (result) => {
224
- if (finished)
225
- return;
226
- finished = true;
227
- clearTimeout(timer);
228
- // TERMINATION FIX: Use simple kill() for Windows compatibility
229
- child.kill();
230
- rl.close();
231
- resolve(result);
232
- };
233
- const fail = (err) => {
234
- if (finished)
235
- return;
236
- finished = true;
237
- clearTimeout(timer);
238
- child.kill();
239
- rl.close();
240
- reject(err);
241
- };
242
- const timer = setTimeout(() => {
243
- done({
244
- matches,
245
- totalMatches: matches.length,
246
- truncated: true,
247
- truncationReason: 'timeout',
248
- searchPattern: params.Query
249
- });
250
- }, GrepSearchTool.SEARCH_TIMEOUT);
251
- rl.on('line', (line) => {
252
- if (finished)
253
- return;
254
- const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
255
- if (matches.length >= maxMatches) {
256
- truncated = true;
257
- // ABORT FIX: Immediately stop and resolve
258
- done({
259
- matches,
260
- totalMatches: matches.length,
261
- truncated,
262
- truncationReason: 'max_matches',
263
- searchPattern: params.Query
264
- });
265
- return;
266
- }
267
- try {
268
- const event = JSON.parse(line);
269
- if (event.type === 'begin' && event.data?.path?.text) {
270
- currentFile = this.normalizePath(event.data.path.text);
271
- contextQueue = [];
272
- pendingMatch = null;
273
- contextAfterCount = 0;
274
- }
275
- else if (event.type === 'context' && event.data?.lines?.text) {
276
- const ctxLine = {
277
- lineNumber: event.data.line_number,
278
- text: this.truncateLine(this.normalizeLineEndings(event.data.lines.text).trimEnd())
279
- };
280
- if (pendingMatch && contextAfterCount < GrepSearchTool.CONTEXT_LINES) {
281
- pendingMatch.contextAfter.push(ctxLine);
282
- contextAfterCount++;
283
- }
284
- else {
285
- contextQueue.push(ctxLine);
286
- if (contextQueue.length > GrepSearchTool.CONTEXT_LINES) {
287
- contextQueue.shift();
288
- }
289
- }
290
- }
291
- else if (event.type === 'match' && event.data) {
292
- // Finalize previous match
293
- if (pendingMatch) {
294
- matches.push(pendingMatch);
295
- }
296
- const lineText = event.data.lines?.text || '';
297
- const submatches = event.data.submatches || [];
298
- pendingMatch = {
299
- file: currentFile || this.normalizePath(event.data.path?.text || ''),
300
- line: event.data.line_number,
301
- column: submatches.length > 0 ? submatches[0].start + 1 : undefined,
302
- byteOffset: event.data.absolute_offset,
303
- match: this.truncateLine(this.normalizeLineEndings(lineText).trimEnd()),
304
- matchIndices: submatches.map((s) => [s.start, s.end]),
305
- contextBefore: [...contextQueue],
306
- contextAfter: []
307
- };
308
- contextQueue = [];
309
- contextAfterCount = 0;
310
- }
311
- }
312
- catch {
313
- // Ignore parse errors
314
- }
315
- });
316
- child.on('close', (code) => {
317
- if (finished)
318
- return;
319
- // Finalize last match
320
- const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
321
- if (pendingMatch && matches.length < maxMatches) {
322
- matches.push(pendingMatch);
323
- }
324
- done({
325
- matches,
326
- totalMatches: matches.length,
327
- truncated,
328
- truncationReason: truncated ? 'max_matches' : undefined,
329
- searchPattern: params.Query
330
- });
331
- });
332
- child.on('error', (err) => {
333
- fail(err);
334
- });
255
+ }
256
+ // ========================================================================
257
+ // RIPGREP BACKEND (Streaming)
258
+ // ========================================================================
259
+ async searchWithRipgrepStreaming(params, cwd) {
260
+ return new Promise((resolve, reject) => {
261
+ const args = [
262
+ "--json",
263
+ `--context=${GrepSearchTool.CONTEXT_LINES}`
264
+ ];
265
+ if (params.CaseInsensitive) {
266
+ args.push("--ignore-case");
267
+ }
268
+ if (!params.IsRegex) {
269
+ args.push("--fixed-strings");
270
+ }
271
+ if (params.Includes && params.Includes.length > 0) {
272
+ params.Includes.forEach((pattern) => {
273
+ args.push("--glob", pattern);
335
274
  });
336
- }
337
- // ========================================================================
338
- // GREP BACKEND (Streaming)
339
- // ========================================================================
340
- async searchWithGrepStreaming(params, cwd) {
341
- return new Promise((resolve, reject) => {
342
- const args = [
343
- '-n', // line numbers
344
- '-r', // recursive
345
- `--context=${GrepSearchTool.CONTEXT_LINES}`,
346
- ];
347
- if (params.CaseInsensitive) {
348
- args.push('-i');
349
- }
350
- if (!params.IsRegex) {
351
- args.push('-F'); // fixed strings
352
- }
353
- else {
354
- args.push('-E'); // extended regex (supports |)
355
- }
356
- if (params.Includes && params.Includes.length > 0) {
357
- params.Includes.forEach(pattern => {
358
- args.push('--include', pattern);
359
- });
360
- }
361
- args.push(params.Query, params.SearchPath);
362
- const matches = [];
363
- let truncated = false;
364
- let finished = false;
365
- const child = spawn('grep', args, {
366
- cwd,
367
- env: { ...process.env, LANG: 'en_US.UTF-8' }
368
- });
369
- const rl = readline.createInterface({ input: child.stdout });
370
- const done = (result) => {
371
- if (finished)
372
- return;
373
- finished = true;
374
- clearTimeout(timer);
375
- child.kill();
376
- rl.close();
377
- resolve(result);
378
- };
379
- const fail = (err) => {
380
- if (finished)
381
- return;
382
- finished = true;
383
- clearTimeout(timer);
384
- child.kill();
385
- rl.close();
386
- reject(err);
387
- };
388
- const timer = setTimeout(() => {
389
- done({
390
- matches,
391
- totalMatches: matches.length,
392
- truncated: true,
393
- truncationReason: 'timeout',
394
- searchPattern: params.Query
395
- });
396
- }, GrepSearchTool.SEARCH_TIMEOUT);
397
- rl.on('line', (line) => {
398
- if (finished)
399
- return;
400
- const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
401
- if (matches.length >= maxMatches) {
402
- truncated = true;
403
- done({
404
- matches,
405
- totalMatches: matches.length,
406
- truncated,
407
- truncationReason: 'max_matches',
408
- searchPattern: params.Query
409
- });
410
- return;
411
- }
412
- const normalizedLine = this.normalizeLineEndings(line);
413
- // Skip separator lines
414
- if (normalizedLine === '--')
415
- return;
416
- const parsed = this.parseGrepLine(normalizedLine);
417
- if (parsed) {
418
- // For grep, we treat each line as a match (context handling is simpler)
419
- matches.push({
420
- file: this.normalizePath(parsed.file),
421
- line: parsed.lineNum,
422
- match: this.truncateLine(parsed.content),
423
- contextBefore: [],
424
- contextAfter: []
425
- });
426
- }
427
- });
428
- child.on('close', () => {
429
- if (finished)
430
- return;
431
- done({
432
- matches,
433
- totalMatches: matches.length,
434
- truncated,
435
- truncationReason: truncated ? 'max_matches' : undefined,
436
- searchPattern: params.Query
437
- });
438
- });
439
- child.on('error', (err) => fail(err));
275
+ }
276
+ args.push("--", params.Query, params.SearchPath);
277
+ const matches = [];
278
+ let truncated = false;
279
+ let currentFile = null;
280
+ let contextQueue = [];
281
+ let pendingMatch = null;
282
+ let contextAfterCount = 0;
283
+ let finished = false;
284
+ const child = spawn("rg", args, {
285
+ cwd,
286
+ env: { ...process.env, LANG: "en_US.UTF-8" }
287
+ });
288
+ const rl = readline.createInterface({ input: child.stdout });
289
+ const done = (result) => {
290
+ if (finished) return;
291
+ finished = true;
292
+ clearTimeout(timer);
293
+ child.kill();
294
+ rl.close();
295
+ resolve(result);
296
+ };
297
+ const fail = (err) => {
298
+ if (finished) return;
299
+ finished = true;
300
+ clearTimeout(timer);
301
+ child.kill();
302
+ rl.close();
303
+ reject(err);
304
+ };
305
+ const timer = setTimeout(() => {
306
+ done({
307
+ matches,
308
+ totalMatches: matches.length,
309
+ truncated: true,
310
+ truncationReason: "timeout",
311
+ searchPattern: params.Query
440
312
  });
441
- }
442
- // ========================================================================
443
- // POWERSHELL BACKEND (Windows)
444
- // ========================================================================
445
- async searchWithPowerShell(params, cwd) {
446
- const psExe = await this.getPowerShellExe();
447
- // Build the PowerShell command
448
- // Select-String supports regex by default, -SimpleMatch for literal
449
- const patternArg = params.IsRegex ? params.Query : params.Query.replace(/'/g, "''");
450
- const searchPath = path.resolve(cwd, params.SearchPath);
451
- let psCommand;
452
- // Check if searching a file or directory
453
- const stats = await fs.promises.stat(searchPath).catch(() => null);
454
- const isDirectory = stats?.isDirectory() ?? false;
455
- // PATH FIX: Use $_.Path instead of $_.Filename for absolute paths
456
- if (isDirectory) {
457
- // Search directory recursively
458
- const includePattern = params.Includes && params.Includes.length > 0
459
- ? params.Includes.map(p => `'${p}'`).join(',')
460
- : "'*'";
461
- const max = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
462
- psCommand = params.IsRegex
463
- ? `Get-ChildItem -Path '${searchPath}' -Recurse -File -Include ${includePattern} -ErrorAction SilentlyContinue | Select-String -Pattern '${patternArg}'${params.CaseInsensitive ? '' : ' -CaseSensitive'} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }`
464
- : `Get-ChildItem -Path '${searchPath}' -Recurse -File -Include ${includePattern} -ErrorAction SilentlyContinue | Select-String -Pattern '${patternArg}' -SimpleMatch${params.CaseInsensitive ? '' : ' -CaseSensitive'} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }`;
465
- }
466
- else {
467
- // Search single file
468
- const max = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
469
- psCommand = params.IsRegex
470
- ? `Select-String -Path '${searchPath}' -Pattern '${patternArg}'${params.CaseInsensitive ? '' : ' -CaseSensitive'} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }`
471
- : `Select-String -Path '${searchPath}' -Pattern '${patternArg}' -SimpleMatch${params.CaseInsensitive ? '' : ' -CaseSensitive'} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }`;
313
+ }, GrepSearchTool.SEARCH_TIMEOUT);
314
+ rl.on("line", (line) => {
315
+ if (finished) return;
316
+ const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
317
+ if (matches.length >= maxMatches) {
318
+ truncated = true;
319
+ done({
320
+ matches,
321
+ totalMatches: matches.length,
322
+ truncated,
323
+ truncationReason: "max_matches",
324
+ searchPattern: params.Query
325
+ });
326
+ return;
472
327
  }
473
- return new Promise((resolve, reject) => {
474
- const matches = [];
475
- let truncated = false;
476
- let finished = false;
477
- const child = spawn(psExe, ['-NoProfile', '-Command', psCommand], {
478
- cwd,
479
- env: { ...process.env }
480
- });
481
- // Robust cleanup function
482
- const done = (result) => {
483
- if (finished)
484
- return;
485
- finished = true;
486
- clearTimeout(timer);
487
- child.kill();
488
- resolve(result);
328
+ try {
329
+ const event = JSON.parse(line);
330
+ if (event.type === "begin" && event.data?.path?.text) {
331
+ currentFile = this.normalizePath(event.data.path.text);
332
+ contextQueue = [];
333
+ pendingMatch = null;
334
+ contextAfterCount = 0;
335
+ } else if (event.type === "context" && event.data?.lines?.text) {
336
+ const ctxLine = {
337
+ lineNumber: event.data.line_number,
338
+ text: this.truncateLine(this.normalizeLineEndings(event.data.lines.text).trimEnd())
489
339
  };
490
- const fail = (err) => {
491
- if (finished)
492
- return;
493
- finished = true;
494
- clearTimeout(timer);
495
- child.kill();
496
- reject(err);
340
+ if (pendingMatch && contextAfterCount < GrepSearchTool.CONTEXT_LINES) {
341
+ pendingMatch.contextAfter.push(ctxLine);
342
+ contextAfterCount++;
343
+ } else {
344
+ contextQueue.push(ctxLine);
345
+ if (contextQueue.length > GrepSearchTool.CONTEXT_LINES) {
346
+ contextQueue.shift();
347
+ }
348
+ }
349
+ } else if (event.type === "match" && event.data) {
350
+ if (pendingMatch) {
351
+ matches.push(pendingMatch);
352
+ }
353
+ const lineText = event.data.lines?.text || "";
354
+ const submatches = event.data.submatches || [];
355
+ pendingMatch = {
356
+ file: currentFile || this.normalizePath(event.data.path?.text || ""),
357
+ line: event.data.line_number,
358
+ column: submatches.length > 0 ? submatches[0].start + 1 : void 0,
359
+ byteOffset: event.data.absolute_offset,
360
+ match: this.truncateLine(this.normalizeLineEndings(lineText).trimEnd()),
361
+ matchIndices: submatches.map((s) => [s.start, s.end]),
362
+ contextBefore: [...contextQueue],
363
+ contextAfter: []
497
364
  };
498
- const timer = setTimeout(() => {
499
- done({
500
- matches,
501
- totalMatches: matches.length,
502
- truncated: true,
503
- truncationReason: 'timeout',
504
- searchPattern: params.Query
505
- });
506
- }, GrepSearchTool.SEARCH_TIMEOUT);
507
- let stdout = '';
508
- let stderr = '';
509
- child.stdout.on('data', (data) => {
510
- stdout += data.toString();
511
- });
512
- child.stderr.on('data', (data) => {
513
- stderr += data.toString();
514
- });
515
- child.on('close', (code) => {
516
- if (finished)
517
- return;
518
- const lines = this.normalizeLineEndings(stdout).split('\n').filter(l => l.trim());
519
- for (const line of lines) {
520
- const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
521
- if (matches.length >= maxMatches) {
522
- truncated = true;
523
- break;
524
- }
525
- const parsed = this.parseGrepLine(line);
526
- if (parsed) {
527
- matches.push({
528
- file: this.normalizePath(parsed.file),
529
- line: parsed.lineNum,
530
- match: this.truncateLine(parsed.content),
531
- contextBefore: [],
532
- contextAfter: []
533
- });
534
- }
535
- }
536
- done({
537
- matches,
538
- totalMatches: matches.length,
539
- truncated,
540
- truncationReason: truncated ? 'max_matches' : undefined,
541
- searchPattern: params.Query
542
- });
543
- });
544
- child.on('error', (err) => fail(err));
545
- });
546
- }
547
- // ========================================================================
548
- // PURE NODE.JS BACKEND (Universal Fallback)
549
- // ========================================================================
550
- async searchWithNodeJS(params, cwd) {
551
- const matches = [];
552
- let truncated = false;
553
- let filesSearched = 0;
554
- let encodingFallback = false;
555
- const searchPath = path.resolve(cwd, params.SearchPath);
556
- const regex = this.createSearchRegex(params.Query, params.IsRegex ?? false, params.CaseInsensitive ?? false);
557
- // Collect files to search
558
- const filesToSearch = [];
559
- const stats = await fs.promises.stat(searchPath).catch(() => null);
560
- if (!stats) {
561
- return { matches: [], totalMatches: 0, truncated: false, searchPattern: params.Query };
562
- }
563
- if (stats.isFile()) {
564
- filesToSearch.push(searchPath);
365
+ contextQueue = [];
366
+ contextAfterCount = 0;
367
+ }
368
+ } catch {
565
369
  }
566
- else if (stats.isDirectory()) {
567
- await this.collectFiles(searchPath, filesToSearch, params.Includes);
568
- }
569
- // Search each file
370
+ });
371
+ child.on("close", (code) => {
372
+ if (finished) return;
570
373
  const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
571
- for (const filePath of filesToSearch) {
572
- if (matches.length >= maxMatches) {
573
- truncated = true;
574
- break;
575
- }
576
- // Skip binary files
577
- if (await this.isBinaryFile(filePath)) {
578
- continue;
579
- }
580
- filesSearched++;
581
- try {
582
- let content;
583
- const buffer = await fs.promises.readFile(filePath);
584
- // Try UTF-8 first
585
- try {
586
- content = buffer.toString('utf8');
587
- // Check for replacement characters indicating decode failure
588
- if (content.includes('\uFFFD')) {
589
- content = buffer.toString('latin1');
590
- encodingFallback = true;
591
- }
592
- }
593
- catch {
594
- content = buffer.toString('latin1');
595
- encodingFallback = true;
596
- }
597
- const lines = this.normalizeLineEndings(content).split('\n');
598
- for (let i = 0; i < lines.length; i++) {
599
- if (matches.length >= maxMatches) {
600
- truncated = true;
601
- break;
602
- }
603
- const line = lines[i];
604
- regex.lastIndex = 0; // Reset regex state
605
- // LOOP FIX: Capture all matches in the line, not just the first
606
- let matchResult;
607
- while ((matchResult = regex.exec(line)) !== null) {
608
- // Prevent infinite loops with zero-width matches
609
- if (matchResult.index === regex.lastIndex) {
610
- regex.lastIndex++;
611
- }
612
- const relativePath = path.relative(cwd, filePath);
613
- // Collect context
614
- const contextBefore = [];
615
- const contextAfter = [];
616
- for (let j = Math.max(0, i - GrepSearchTool.CONTEXT_LINES); j < i; j++) {
617
- contextBefore.push({
618
- lineNumber: j + 1,
619
- text: this.truncateLine(lines[j])
620
- });
621
- }
622
- for (let j = i + 1; j <= Math.min(lines.length - 1, i + GrepSearchTool.CONTEXT_LINES); j++) {
623
- contextAfter.push({
624
- lineNumber: j + 1,
625
- text: this.truncateLine(lines[j])
626
- });
627
- }
628
- matches.push({
629
- file: this.normalizePath(relativePath),
630
- line: i + 1,
631
- column: matchResult.index + 1,
632
- match: this.truncateLine(line),
633
- contextBefore,
634
- contextAfter
635
- });
636
- if (matches.length >= maxMatches) {
637
- truncated = true;
638
- break;
639
- }
640
- }
641
- if (truncated)
642
- break;
643
- }
644
- }
645
- catch {
646
- // Skip files that can't be read
647
- }
374
+ if (pendingMatch && matches.length < maxMatches) {
375
+ matches.push(pendingMatch);
648
376
  }
649
- return {
377
+ done({
378
+ matches,
379
+ totalMatches: matches.length,
380
+ truncated,
381
+ truncationReason: truncated ? "max_matches" : void 0,
382
+ searchPattern: params.Query
383
+ });
384
+ });
385
+ child.on("error", (err) => {
386
+ fail(err);
387
+ });
388
+ });
389
+ }
390
+ // ========================================================================
391
+ // GREP BACKEND (Streaming)
392
+ // ========================================================================
393
+ async searchWithGrepStreaming(params, cwd) {
394
+ return new Promise((resolve, reject) => {
395
+ const query = params.IsRegex ? normalizeRegexForGrep(params.Query) : params.Query;
396
+ const args = [
397
+ "-n",
398
+ // line numbers
399
+ "-r",
400
+ // recursive
401
+ `--context=${GrepSearchTool.CONTEXT_LINES}`
402
+ ];
403
+ if (params.CaseInsensitive) {
404
+ args.push("-i");
405
+ }
406
+ if (!params.IsRegex) {
407
+ args.push("-F");
408
+ } else {
409
+ args.push("-E");
410
+ }
411
+ if (params.Includes && params.Includes.length > 0) {
412
+ params.Includes.forEach((pattern) => {
413
+ args.push("--include", pattern);
414
+ });
415
+ }
416
+ args.push(query, params.SearchPath);
417
+ const matches = [];
418
+ let truncated = false;
419
+ let finished = false;
420
+ const child = spawn("grep", args, {
421
+ cwd,
422
+ env: { ...process.env, LANG: "en_US.UTF-8" }
423
+ });
424
+ const rl = readline.createInterface({ input: child.stdout });
425
+ const done = (result) => {
426
+ if (finished) return;
427
+ finished = true;
428
+ clearTimeout(timer);
429
+ child.kill();
430
+ rl.close();
431
+ resolve(result);
432
+ };
433
+ const fail = (err) => {
434
+ if (finished) return;
435
+ finished = true;
436
+ clearTimeout(timer);
437
+ child.kill();
438
+ rl.close();
439
+ reject(err);
440
+ };
441
+ const timer = setTimeout(() => {
442
+ done({
443
+ matches,
444
+ totalMatches: matches.length,
445
+ truncated: true,
446
+ truncationReason: "timeout",
447
+ searchPattern: params.Query
448
+ });
449
+ }, GrepSearchTool.SEARCH_TIMEOUT);
450
+ rl.on("line", (line) => {
451
+ if (finished) return;
452
+ const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
453
+ if (matches.length >= maxMatches) {
454
+ truncated = true;
455
+ done({
650
456
  matches,
651
457
  totalMatches: matches.length,
652
458
  truncated,
653
- truncationReason: truncated ? 'max_matches' : undefined,
654
- searchPattern: params.Query,
655
- filesSearched,
656
- encodingFallback
657
- };
658
- }
659
- /**
660
- * Recursively collect files from directory
661
- */
662
- async collectFiles(dir, files, includes, maxFiles = 10000) {
663
- if (files.length >= maxFiles)
664
- return;
665
- try {
666
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
667
- for (const entry of entries) {
668
- if (files.length >= maxFiles)
669
- break;
670
- const fullPath = path.join(dir, entry.name);
671
- // Skip common non-code directories
672
- if (entry.isDirectory()) {
673
- if (['node_modules', '.git', 'dist', 'build', '__pycache__', '.vscode', '.idea'].includes(entry.name)) {
674
- continue;
675
- }
676
- await this.collectFiles(fullPath, files, includes, maxFiles);
677
- }
678
- else if (entry.isFile()) {
679
- if (this.matchesIncludesFilter(fullPath, includes)) {
680
- files.push(fullPath);
681
- }
682
- }
683
- }
459
+ truncationReason: "max_matches",
460
+ searchPattern: params.Query
461
+ });
462
+ return;
684
463
  }
685
- catch {
686
- // Ignore directories that can't be read
464
+ const normalizedLine = this.normalizeLineEndings(line);
465
+ if (normalizedLine === "--") return;
466
+ const parsed = this.parseGrepLine(normalizedLine);
467
+ if (parsed) {
468
+ matches.push({
469
+ file: this.normalizePath(parsed.file),
470
+ line: parsed.lineNum,
471
+ match: this.truncateLine(parsed.content),
472
+ contextBefore: [],
473
+ contextAfter: []
474
+ });
687
475
  }
476
+ });
477
+ child.on("close", () => {
478
+ if (finished) return;
479
+ done({
480
+ matches,
481
+ totalMatches: matches.length,
482
+ truncated,
483
+ truncationReason: truncated ? "max_matches" : void 0,
484
+ searchPattern: params.Query
485
+ });
486
+ });
487
+ child.on("error", (err) => fail(err));
488
+ });
489
+ }
490
+ // ========================================================================
491
+ // POWERSHELL BACKEND (Windows)
492
+ // ========================================================================
493
+ async searchWithPowerShell(params, cwd) {
494
+ const psExe = await this.getPowerShellExe();
495
+ const patternArg = escapePowerShellSingleQuoted(params.Query);
496
+ const searchPath = path.resolve(cwd, params.SearchPath);
497
+ const escapedSearchPath = escapePowerShellSingleQuoted(searchPath);
498
+ let psCommand;
499
+ const stats = await fs.promises.stat(searchPath).catch(() => null);
500
+ const isDirectory = stats?.isDirectory() ?? false;
501
+ if (isDirectory) {
502
+ const includePattern = params.Includes && params.Includes.length > 0 ? params.Includes.map((p) => `'${escapePowerShellSingleQuoted(p)}'`).join(",") : "'*'";
503
+ const max = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
504
+ psCommand = params.IsRegex ? `Get-ChildItem -Path '${escapedSearchPath}' -Recurse -File -Include ${includePattern} -ErrorAction SilentlyContinue | Select-String -Pattern '${patternArg}'${params.CaseInsensitive ? "" : " -CaseSensitive"} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }` : `Get-ChildItem -Path '${escapedSearchPath}' -Recurse -File -Include ${includePattern} -ErrorAction SilentlyContinue | Select-String -Pattern '${patternArg}' -SimpleMatch${params.CaseInsensitive ? "" : " -CaseSensitive"} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }`;
505
+ } else {
506
+ const max = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
507
+ psCommand = params.IsRegex ? `Select-String -Path '${escapedSearchPath}' -Pattern '${patternArg}'${params.CaseInsensitive ? "" : " -CaseSensitive"} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }` : `Select-String -Path '${escapedSearchPath}' -Pattern '${patternArg}' -SimpleMatch${params.CaseInsensitive ? "" : " -CaseSensitive"} -Context ${GrepSearchTool.CONTEXT_LINES} | Select-Object -First ${max} | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }`;
508
+ }
509
+ return new Promise((resolve, reject) => {
510
+ const matches = [];
511
+ let truncated = false;
512
+ let finished = false;
513
+ const child = spawn(psExe, ["-NoProfile", "-Command", psCommand], {
514
+ cwd,
515
+ env: { ...process.env }
516
+ });
517
+ const done = (result) => {
518
+ if (finished) return;
519
+ finished = true;
520
+ clearTimeout(timer);
521
+ child.kill();
522
+ resolve(result);
523
+ };
524
+ const fail = (err) => {
525
+ if (finished) return;
526
+ finished = true;
527
+ clearTimeout(timer);
528
+ child.kill();
529
+ reject(err);
530
+ };
531
+ const timer = setTimeout(() => {
532
+ done({
533
+ matches,
534
+ totalMatches: matches.length,
535
+ truncated: true,
536
+ truncationReason: "timeout",
537
+ searchPattern: params.Query
538
+ });
539
+ }, GrepSearchTool.SEARCH_TIMEOUT);
540
+ let stdout = "";
541
+ let stderr = "";
542
+ child.stdout.on("data", (data) => {
543
+ stdout += data.toString();
544
+ });
545
+ child.stderr.on("data", (data) => {
546
+ stderr += data.toString();
547
+ });
548
+ child.on("close", (code) => {
549
+ if (finished) return;
550
+ const lines = this.normalizeLineEndings(stdout).split("\n").filter((l) => l.trim());
551
+ for (const line of lines) {
552
+ const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
553
+ if (matches.length >= maxMatches) {
554
+ truncated = true;
555
+ break;
556
+ }
557
+ const parsed = this.parseGrepLine(line);
558
+ if (parsed) {
559
+ matches.push({
560
+ file: this.normalizePath(parsed.file),
561
+ line: parsed.lineNum,
562
+ match: this.truncateLine(parsed.content),
563
+ contextBefore: [],
564
+ contextAfter: []
565
+ });
566
+ }
567
+ }
568
+ done({
569
+ matches,
570
+ totalMatches: matches.length,
571
+ truncated,
572
+ truncationReason: truncated ? "max_matches" : void 0,
573
+ searchPattern: params.Query
574
+ });
575
+ });
576
+ child.on("error", (err) => fail(err));
577
+ });
578
+ }
579
+ // ========================================================================
580
+ // PURE NODE.JS BACKEND (Universal Fallback)
581
+ // ========================================================================
582
+ async searchWithNodeJS(params, cwd) {
583
+ const matches = [];
584
+ let truncated = false;
585
+ let filesSearched = 0;
586
+ let encodingFallback = false;
587
+ const searchPath = path.resolve(cwd, params.SearchPath);
588
+ const regex = this.createSearchRegex(params.Query, params.IsRegex ?? false, params.CaseInsensitive ?? false);
589
+ const filesToSearch = [];
590
+ const stats = await fs.promises.stat(searchPath).catch(() => null);
591
+ if (!stats) {
592
+ return { matches: [], totalMatches: 0, truncated: false, searchPattern: params.Query };
593
+ }
594
+ if (stats.isFile()) {
595
+ filesToSearch.push(searchPath);
596
+ } else if (stats.isDirectory()) {
597
+ await this.collectFiles(searchPath, filesToSearch, params.Includes);
688
598
  }
689
- // ========================================================================
690
- // MAIN EXECUTE METHOD
691
- // ========================================================================
692
- async execute(params, cwd) {
693
- let result;
694
- let backend;
599
+ const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
600
+ for (const filePath of filesToSearch) {
601
+ if (matches.length >= maxMatches) {
602
+ truncated = true;
603
+ break;
604
+ }
605
+ if (await this.isBinaryFile(filePath)) {
606
+ continue;
607
+ }
608
+ filesSearched++;
609
+ try {
610
+ let content;
611
+ const buffer = await fs.promises.readFile(filePath);
695
612
  try {
696
- // Try backends in order of preference
697
- if (await this.hasRipgrep()) {
698
- backend = 'ripgrep';
699
- result = await this.searchWithRipgrepStreaming(params, cwd);
613
+ content = buffer.toString("utf8");
614
+ if (content.includes("\uFFFD")) {
615
+ content = buffer.toString("latin1");
616
+ encodingFallback = true;
617
+ }
618
+ } catch {
619
+ content = buffer.toString("latin1");
620
+ encodingFallback = true;
621
+ }
622
+ const lines = this.normalizeLineEndings(content).split("\n");
623
+ for (let i = 0; i < lines.length; i++) {
624
+ if (matches.length >= maxMatches) {
625
+ truncated = true;
626
+ break;
627
+ }
628
+ const line = lines[i];
629
+ regex.lastIndex = 0;
630
+ let matchResult;
631
+ while ((matchResult = regex.exec(line)) !== null) {
632
+ if (matchResult.index === regex.lastIndex) {
633
+ regex.lastIndex++;
700
634
  }
701
- else if (await this.hasGrep()) {
702
- backend = 'grep';
703
- result = await this.searchWithGrepStreaming(params, cwd);
635
+ const relativePath = path.relative(cwd, filePath);
636
+ const contextBefore = [];
637
+ const contextAfter = [];
638
+ for (let j = Math.max(0, i - GrepSearchTool.CONTEXT_LINES); j < i; j++) {
639
+ contextBefore.push({
640
+ lineNumber: j + 1,
641
+ text: this.truncateLine(lines[j])
642
+ });
704
643
  }
705
- else if (process.platform === 'win32' && await this.hasPowerShell()) {
706
- backend = 'powershell';
707
- result = await this.searchWithPowerShell(params, cwd);
644
+ for (let j = i + 1; j <= Math.min(lines.length - 1, i + GrepSearchTool.CONTEXT_LINES); j++) {
645
+ contextAfter.push({
646
+ lineNumber: j + 1,
647
+ text: this.truncateLine(lines[j])
648
+ });
708
649
  }
709
- else {
710
- // Universal Node.js fallback
711
- backend = 'nodejs';
712
- result = await this.searchWithNodeJS(params, cwd);
650
+ matches.push({
651
+ file: this.normalizePath(relativePath),
652
+ line: i + 1,
653
+ column: matchResult.index + 1,
654
+ match: this.truncateLine(line),
655
+ contextBefore,
656
+ contextAfter
657
+ });
658
+ if (matches.length >= maxMatches) {
659
+ truncated = true;
660
+ break;
713
661
  }
662
+ }
663
+ if (truncated) break;
714
664
  }
715
- catch (error) {
716
- // If primary backend fails, try fallback
717
- try {
718
- backend = 'nodejs';
719
- result = await this.searchWithNodeJS(params, cwd);
720
- }
721
- catch (fallbackError) {
722
- throw new Error(`Search failed: ${error.message}. Fallback also failed: ${fallbackError.message}`);
723
- }
665
+ } catch {
666
+ }
667
+ }
668
+ return {
669
+ matches,
670
+ totalMatches: matches.length,
671
+ truncated,
672
+ truncationReason: truncated ? "max_matches" : void 0,
673
+ searchPattern: params.Query,
674
+ filesSearched,
675
+ encodingFallback
676
+ };
677
+ }
678
+ /**
679
+ * Recursively collect files from directory
680
+ */
681
+ async collectFiles(dir, files, includes, maxFiles = 1e4) {
682
+ if (files.length >= maxFiles) return;
683
+ try {
684
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
685
+ for (const entry of entries) {
686
+ if (files.length >= maxFiles) break;
687
+ const fullPath = path.join(dir, entry.name);
688
+ if (entry.isDirectory()) {
689
+ if (["node_modules", ".git", "dist", "build", "__pycache__", ".vscode", ".idea"].includes(entry.name)) {
690
+ continue;
691
+ }
692
+ await this.collectFiles(fullPath, files, includes, maxFiles);
693
+ } else if (entry.isFile()) {
694
+ if (this.matchesIncludesFilter(fullPath, includes)) {
695
+ files.push(fullPath);
696
+ }
724
697
  }
725
- // Apply post-filtering for Includes if backend didn't support it
726
- if (params.Includes && params.Includes.length > 0 && backend === 'nodejs') {
727
- const filteredMatches = result.matches.filter(match => this.matchesIncludesFilter(match.file, params.Includes));
728
- return {
729
- ...result,
730
- matches: filteredMatches,
731
- totalMatches: filteredMatches.length,
732
- backend
733
- };
698
+ }
699
+ } catch {
700
+ }
701
+ }
702
+ // ========================================================================
703
+ // MAIN EXECUTE METHOD
704
+ // ========================================================================
705
+ async executeWithPreferredBackends(params, cwd) {
706
+ let result;
707
+ let backend;
708
+ try {
709
+ if (await this.hasRipgrep()) {
710
+ backend = "ripgrep";
711
+ result = await this.searchWithRipgrepStreaming(params, cwd);
712
+ } else if (await this.hasGrep()) {
713
+ if (params.IsRegex && usesRegexFeaturesUnsupportedByGrep(params.Query)) {
714
+ backend = "nodejs";
715
+ result = await this.searchWithNodeJS(params, cwd);
716
+ } else {
717
+ backend = "grep";
718
+ result = await this.searchWithGrepStreaming(params, cwd);
734
719
  }
735
- return { ...result, backend };
720
+ } else if (process.platform === "win32" && await this.hasPowerShell()) {
721
+ backend = "powershell";
722
+ result = await this.searchWithPowerShell(params, cwd);
723
+ } else {
724
+ backend = "nodejs";
725
+ result = await this.searchWithNodeJS(params, cwd);
726
+ }
727
+ } catch (error) {
728
+ try {
729
+ backend = "nodejs";
730
+ result = await this.searchWithNodeJS(params, cwd);
731
+ } catch (fallbackError) {
732
+ throw new Error(`Search failed: ${error.message}. Fallback also failed: ${fallbackError.message}`);
733
+ }
734
+ }
735
+ if (params.Includes && params.Includes.length > 0 && backend === "nodejs") {
736
+ const filteredMatches = result.matches.filter(
737
+ (match) => this.matchesIncludesFilter(match.file, params.Includes)
738
+ );
739
+ return {
740
+ ...result,
741
+ matches: filteredMatches,
742
+ totalMatches: filteredMatches.length,
743
+ backend
744
+ };
745
+ }
746
+ return { ...result, backend };
747
+ }
748
+ async execute(params, cwd) {
749
+ const shouldRetryAsRegex = params.IsRegex === void 0 && looksLikeRegexPattern(params.Query);
750
+ const primaryParams = {
751
+ ...params,
752
+ IsRegex: params.IsRegex ?? false
753
+ };
754
+ const primaryResult = await this.executeWithPreferredBackends(primaryParams, cwd);
755
+ if (shouldRetryAsRegex && primaryResult.matches.length === 0) {
756
+ const regexParams = {
757
+ ...params,
758
+ IsRegex: true
759
+ };
760
+ const regexResult = await this.executeWithPreferredBackends(regexParams, cwd);
761
+ if (regexResult.matches.length > 0) {
762
+ return regexResult;
763
+ }
736
764
  }
765
+ return primaryResult;
766
+ }
737
767
  }
738
- // ============================================================================
739
- // FORMATTING FUNCTION
740
- // ============================================================================
741
768
  function formatGrepResults(result) {
742
- if (result.matches.length === 0) {
743
- return `No matches found for pattern "${result.searchPattern}"`;
769
+ if (result.matches.length === 0) {
770
+ return `No matches found for pattern "${result.searchPattern}"`;
771
+ }
772
+ const uniqueFiles = /* @__PURE__ */ new Set();
773
+ result.matches.forEach((match) => uniqueFiles.add(match.file));
774
+ const fileCount = uniqueFiles.size;
775
+ const output = [];
776
+ output.push(`Found ${result.uniqueMatchesCount} match${result.uniqueMatchesCount === 1 ? "" : "es"} in ${fileCount} file${fileCount === 1 ? "" : "s"} for pattern "${result.searchPattern}"`);
777
+ if (result.truncated) {
778
+ const reason = result.truncationReason === "timeout" ? "(timed out)" : "";
779
+ output.push(`(showing first ${result.matches.length} matches${reason ? " " + reason : ""})`);
780
+ }
781
+ const matchesByFile = /* @__PURE__ */ new Map();
782
+ result.matches.forEach((match) => {
783
+ if (!matchesByFile.has(match.file)) {
784
+ matchesByFile.set(match.file, []);
744
785
  }
745
- const uniqueFiles = new Set();
746
- result.matches.forEach(match => uniqueFiles.add(match.file));
747
- const fileCount = uniqueFiles.size;
748
- const output = [];
749
- output.push(`Found ${result.uniqueMatchesCount} match${result.uniqueMatchesCount === 1 ? '' : 'es'} in ${fileCount} file${fileCount === 1 ? '' : 's'} for pattern "${result.searchPattern}"`);
750
- if (result.truncated) {
751
- const reason = result.truncationReason === 'timeout' ? '(timed out)' : '';
752
- output.push(`(showing first ${result.matches.length} matches${reason ? ' ' + reason : ''})`);
753
- }
754
- const matchesByFile = new Map();
755
- result.matches.forEach(match => {
756
- if (!matchesByFile.has(match.file)) {
757
- matchesByFile.set(match.file, []);
758
- }
759
- matchesByFile.get(match.file)?.push(match);
786
+ matchesByFile.get(match.file)?.push(match);
787
+ });
788
+ matchesByFile.forEach((matches, file) => {
789
+ output.push(`
790
+ ${file}`);
791
+ matches.forEach((match) => {
792
+ match.contextBefore.forEach((ctx) => {
793
+ output.push(`${ctx.lineNumber}: ${ctx.text}`);
794
+ });
795
+ const columnInfo = match.column ? ` (col ${match.column})` : "";
796
+ output.push(`${match.line}:> ${match.match}${columnInfo}`);
797
+ match.contextAfter.forEach((ctx) => {
798
+ output.push(`${ctx.lineNumber}: ${ctx.text}`);
799
+ });
760
800
  });
761
- matchesByFile.forEach((matches, file) => {
762
- output.push(`\n${file}`);
763
- matches.forEach(match => {
764
- match.contextBefore.forEach(ctx => {
765
- output.push(`${ctx.lineNumber}: ${ctx.text}`);
766
- });
767
- const columnInfo = match.column ? ` (col ${match.column})` : '';
768
- output.push(`${match.line}:> ${match.match}${columnInfo}`);
769
- match.contextAfter.forEach(ctx => {
770
- output.push(`${ctx.lineNumber}: ${ctx.text}`);
771
- });
772
- });
773
- });
774
- return output.join('\n');
801
+ });
802
+ return output.join("\n");
775
803
  }
776
- // ============================================================================
777
- // REMOTE SEARCH HELPER
778
- // ============================================================================
779
- /**
780
- * Execute grep search on a remote system via ContextManager
781
- * Used for SSH/Docker/WSL environments
782
- */
783
804
  async function searchRemote(params, contextManager) {
784
- const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
785
- // Escape the query for shell usage
786
- const escapedQuery = params.Query.replace(/'/g, "'\\''");
787
- // First, check if the path is a file or directory to use correct grep flags
788
- // Use test -d to check if it's a directory
789
- const checkResult = await contextManager.executeCommand(`test -d "${params.SearchPath}" && echo "dir" || echo "file"`, 5000);
790
- const isDirectory = checkResult.stdout.trim() === 'dir';
791
- // Build grep command - use -r only for directories, -H to always show filename
792
- let grepArgs = isDirectory ? '-rn' : '-Hn'; // -H for filename, -n for line numbers, -r for recursive
793
- if (params.CaseInsensitive) {
794
- grepArgs += 'i';
795
- }
796
- if (!params.IsRegex) {
797
- grepArgs += 'F'; // fixed strings (literal)
798
- }
799
- // Add include patterns if specified (only useful for directories)
800
- let includeArgs = '';
801
- if (isDirectory && params.Includes && params.Includes.length > 0) {
802
- includeArgs = params.Includes.map(p => `--include='${p}'`).join(' ');
803
- }
804
- // Build the full command with result limit
805
- const command = `grep ${grepArgs} ${includeArgs} '${escapedQuery}' "${params.SearchPath}" 2>/dev/null | head -n ${maxMatches}`;
806
- try {
807
- const result = await contextManager.executeCommand(command, 30000);
808
- const matches = [];
809
- const lines = result.stdout.split('\n').filter(line => line.trim());
810
- for (const line of lines) {
811
- // Parse grep output: file:line:content
812
- const match = line.match(/^(.+?):(\d+):(.+)$/);
813
- if (match) {
814
- // Truncate long lines
815
- let content = match[3];
816
- if (content.length > 300) {
817
- content = content.substring(0, 300) + ' [truncated]';
818
- }
819
- matches.push({
820
- file: match[1],
821
- line: parseInt(match[2], 10),
822
- match: content,
823
- contextBefore: [],
824
- contextAfter: [],
825
- });
826
- }
805
+ const maxMatches = params.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
806
+ const remoteQuery = params.IsRegex ? normalizeRegexForGrep(params.Query) : params.Query;
807
+ const escapedQuery = remoteQuery.replace(/'/g, "'\\''");
808
+ const checkResult = await contextManager.executeCommand(`test -d "${params.SearchPath}" && echo "dir" || echo "file"`, 5e3);
809
+ const isDirectory = checkResult.stdout.trim() === "dir";
810
+ let grepArgs = isDirectory ? "-rn" : "-Hn";
811
+ if (params.CaseInsensitive) {
812
+ grepArgs += "i";
813
+ }
814
+ if (params.IsRegex) {
815
+ grepArgs += "E";
816
+ } else {
817
+ grepArgs += "F";
818
+ }
819
+ let includeArgs = "";
820
+ if (isDirectory && params.Includes && params.Includes.length > 0) {
821
+ includeArgs = params.Includes.map((p) => `--include='${p}'`).join(" ");
822
+ }
823
+ const command = `grep ${grepArgs} ${includeArgs} '${escapedQuery}' "${params.SearchPath}" 2>/dev/null | head -n ${maxMatches}`;
824
+ try {
825
+ const result = await contextManager.executeCommand(command, 3e4);
826
+ const matches = [];
827
+ const lines = result.stdout.split("\n").filter((line) => line.trim());
828
+ for (const line of lines) {
829
+ const match = line.match(/^(.+?):(\d+):(.+)$/);
830
+ if (match) {
831
+ let content = match[3];
832
+ if (content.length > 300) {
833
+ content = content.substring(0, 300) + " [truncated]";
827
834
  }
828
- return {
829
- matches,
830
- totalMatches: matches.length,
831
- truncated: matches.length >= maxMatches,
832
- truncationReason: matches.length >= maxMatches ? 'max_matches' : undefined,
833
- searchPattern: params.Query,
834
- backend: 'remote',
835
- };
836
- }
837
- catch (error) {
838
- // If grep fails (command not found, permission denied, etc.), return empty result
839
- return {
840
- matches: [],
841
- totalMatches: 0,
842
- truncated: false,
843
- searchPattern: params.Query,
844
- backend: 'remote',
845
- };
835
+ matches.push({
836
+ file: match[1],
837
+ line: parseInt(match[2], 10),
838
+ match: content,
839
+ contextBefore: [],
840
+ contextAfter: []
841
+ });
842
+ }
846
843
  }
844
+ return {
845
+ matches,
846
+ totalMatches: matches.length,
847
+ truncated: matches.length >= maxMatches,
848
+ truncationReason: matches.length >= maxMatches ? "max_matches" : void 0,
849
+ searchPattern: params.Query,
850
+ backend: "remote"
851
+ };
852
+ } catch (error) {
853
+ return {
854
+ matches: [],
855
+ totalMatches: 0,
856
+ truncated: false,
857
+ searchPattern: params.Query,
858
+ backend: "remote"
859
+ };
860
+ }
847
861
  }
848
- // ============================================================================
849
- // TOOL EXPORT
850
- // ============================================================================
851
- export const grepSearchTool = {
852
- schema: {
853
- name: 'grep_search',
854
- description: `Search FILE CONTENTS for text patterns using ripgrep.
855
-
856
- USE THIS TOOL WHEN:
857
- - Searching for text/code INSIDE files (function names, variable names, error messages, etc.)
858
- - Looking for where something is defined or used in the codebase
859
- - Finding all files that contain a specific string or pattern
860
-
861
- MULTI-WORD SEARCH: When you search for "control system", this tool will automatically:
862
- 1. Search for the exact phrase "control system"
863
- 2. ALSO search for files containing just "control" or just "system"
864
- 3. Merge and deduplicate all results
865
-
866
- Results are returned with file paths and line numbers:
867
- filename
868
- line: context
869
- line:> match (col X)
870
- line: context
871
-
872
- NOT for: Finding files/folders by NAME. Use find_files for that instead.
862
+ const grepSearchTool = {
863
+ schema: {
864
+ name: "grep_search",
865
+ description: `Search FILE CONTENTS for text patterns using ripgrep.
866
+
867
+ USE THIS TOOL WHEN:
868
+ - Searching for text/code INSIDE files (function names, variable names, error messages, etc.)
869
+ - Looking for where something is defined or used in the codebase
870
+ - Finding all files that contain a specific string or pattern
871
+
872
+ MULTI-WORD SEARCH: When you search for "control system", this tool will automatically:
873
+ 1. Search for the exact phrase "control system"
874
+ 2. ALSO search for files containing just "control" or just "system"
875
+ 3. Merge and deduplicate all results
876
+
877
+ Results are returned with file paths and line numbers:
878
+ filename
879
+ line: context
880
+ line:> match (col X)
881
+ line: context
882
+
883
+ NOT for: Finding files/folders by NAME. Use find_files for that instead.
873
884
  Total results are capped at 50 matches. Use Includes to filter by file type.`,
874
- parameters: {
875
- type: 'object',
876
- properties: {
877
- reason_text: {
878
- type: 'string',
879
- description: 'REQUIRED: A brief explanation of what you are searching for and why.',
880
- },
881
- Query: {
882
- type: 'string',
883
- description: 'The search term or pattern. For multi-word queries like "control system", individual words will also be searched.',
884
- },
885
- SearchPath: {
886
- type: 'string',
887
- description: 'The path to search. This can be a directory or a file.',
888
- },
889
- Includes: {
890
- type: 'array',
891
- description: 'Glob patterns to filter files (e.g. ["*.ts"]).',
892
- items: { type: 'string' }
893
- },
894
- MaxResults: {
895
- type: 'number',
896
- description: 'Optional: Maximum number of results to return. Default is 50. Increase this if you need to see more matches.',
897
- },
898
- CaseInsensitive: {
899
- type: 'boolean',
900
- description: 'If true, performs a case-insensitive search. Default: false (case-sensitive)',
901
- },
902
- IsRegex: {
903
- type: 'boolean',
904
- description: 'If true, treats Query as a regular expression. Default: false',
905
- },
906
- },
907
- required: ['reason_text', 'Query', 'SearchPath'],
885
+ parameters: {
886
+ type: "object",
887
+ properties: {
888
+ reason_text: {
889
+ type: "string",
890
+ description: "REQUIRED: A brief explanation of what you are searching for and why."
908
891
  },
909
- },
910
- async execute(args, context) {
911
- const tool = new GrepSearchTool();
912
- const startTime = Date.now();
913
- const query = args.Query;
914
- const words = query.trim().split(/\s+/).filter(w => w.length > 2);
915
- const allMatches = [];
916
- const seenMatches = new Set();
917
- const matchesPerQuery = {};
918
- const queriesExecuted = [];
919
- let rawTotalMatches = 0;
920
- let backend = 'ripgrep';
921
- let truncated = false;
922
- let truncationReason;
923
- let encodingFallback = false;
924
- const queries = [query];
925
- if (words.length > 1 && !args.IsRegex) {
926
- for (const word of words) {
927
- if (word !== query) {
928
- queries.push(word);
929
- }
930
- }
892
+ Query: {
893
+ type: "string",
894
+ description: 'The search term or pattern. For multi-word queries like "control system", individual words will also be searched.'
895
+ },
896
+ SearchPath: {
897
+ type: "string",
898
+ description: "The path to search. This can be a directory or a file."
899
+ },
900
+ Includes: {
901
+ type: "array",
902
+ description: 'Glob patterns to filter files (e.g. ["*.ts"]).',
903
+ items: { type: "string" }
904
+ },
905
+ MaxResults: {
906
+ type: "number",
907
+ description: "Optional: Maximum number of results to return. Default is 50. Increase this if you need to see more matches."
908
+ },
909
+ CaseInsensitive: {
910
+ type: "boolean",
911
+ description: "If true, performs a case-insensitive search. Default: false (case-sensitive)"
912
+ },
913
+ IsRegex: {
914
+ type: "boolean",
915
+ description: "If true, treats Query as regex. If omitted, the tool starts with literal search and auto-retries regex for regex-like patterns (e.g. a|b, loadChat\\()."
931
916
  }
932
- // Check if we're in a remote context (SSH, Docker, WSL)
933
- const contextManager = context.contextManager;
934
- const currentContext = contextManager?.getCurrentContext();
935
- const isRemote = currentContext && currentContext.type !== 'local';
917
+ },
918
+ required: ["reason_text", "Query", "SearchPath"]
919
+ }
920
+ },
921
+ async execute(args, context) {
922
+ const tool = new GrepSearchTool();
923
+ const startTime = Date.now();
924
+ const query = args.Query;
925
+ const queryLooksRegexLike = looksLikeRegexPattern(query);
926
+ const words = query.trim().split(/\s+/).filter((w) => w.length > 2);
927
+ const allMatches = [];
928
+ const seenMatches = /* @__PURE__ */ new Set();
929
+ const matchesPerQuery = {};
930
+ const queriesExecuted = [];
931
+ let rawTotalMatches = 0;
932
+ let backend = "ripgrep";
933
+ let truncated = false;
934
+ let truncationReason;
935
+ let encodingFallback = false;
936
+ const queries = [query];
937
+ if (words.length > 1 && args.IsRegex !== true && !queryLooksRegexLike) {
938
+ for (const word of words) {
939
+ if (word !== query) {
940
+ queries.push(word);
941
+ }
942
+ }
943
+ }
944
+ const contextManager = context.contextManager;
945
+ const currentContext = contextManager?.getCurrentContext();
946
+ const isRemote = currentContext && currentContext.type !== "local";
947
+ try {
948
+ for (const searchQuery of queries) {
949
+ queriesExecuted.push(searchQuery);
950
+ const params = {
951
+ Query: searchQuery,
952
+ SearchPath: args.SearchPath,
953
+ Includes: args.Includes,
954
+ MaxResults: args.MaxResults,
955
+ CaseInsensitive: args.CaseInsensitive ?? false,
956
+ IsRegex: args.IsRegex
957
+ };
936
958
  try {
937
- for (const searchQuery of queries) {
938
- queriesExecuted.push(searchQuery);
939
- const params = {
940
- Query: searchQuery,
941
- SearchPath: args.SearchPath,
942
- Includes: args.Includes,
943
- MaxResults: args.MaxResults,
944
- CaseInsensitive: args.CaseInsensitive ?? false,
945
- IsRegex: args.IsRegex,
946
- };
947
- try {
948
- // Use remote search for SSH/Docker/WSL, local search otherwise
949
- const result = isRemote
950
- ? await searchRemote(params, contextManager)
951
- : await tool.execute(params, context.cwd);
952
- backend = result.backend;
953
- matchesPerQuery[searchQuery] = result.totalMatches;
954
- rawTotalMatches += result.totalMatches;
955
- if (result.truncated) {
956
- truncated = true;
957
- truncationReason = result.truncationReason;
958
- }
959
- if (result.encodingFallback) {
960
- encodingFallback = true;
961
- }
962
- // Add unique matches with improved dedupe key
963
- for (const match of result.matches) {
964
- // Use file:line:column:matchText for more accurate deduplication
965
- const key = `${match.file}:${match.line}:${match.column ?? 0}:${match.match.substring(0, 50)}`;
966
- const maxMatches = args.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
967
- if (!seenMatches.has(key) && allMatches.length < maxMatches) {
968
- seenMatches.add(key);
969
- allMatches.push(match);
970
- }
971
- }
972
- }
973
- catch {
974
- matchesPerQuery[searchQuery] = 0;
975
- continue;
976
- }
977
- }
978
- const endTime = Date.now();
979
- const uniqueMatchesCount = allMatches.length;
980
- const max = args.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
981
- const finalResult = {
982
- matches: allMatches,
983
- totalMatches: rawTotalMatches,
984
- uniqueMatchesCount,
985
- truncated: truncated || allMatches.length >= max,
986
- truncationReason: allMatches.length >= max ? 'max_matches' : truncationReason,
987
- searchPattern: queries.length > 1 ? `"${query}" (+ individual words)` : query,
988
- metadata: {
989
- backend,
990
- searchDurationMs: endTime - startTime,
991
- queriesExecuted,
992
- matchesPerQuery,
993
- encodingFallback
994
- },
995
- formattedOutput: '',
959
+ let result = isRemote ? await searchRemote(params, contextManager) : await tool.execute(params, context.cwd);
960
+ if (isRemote && args.IsRegex === void 0 && looksLikeRegexPattern(searchQuery) && result.matches.length === 0) {
961
+ const regexRetryParams = {
962
+ ...params,
963
+ IsRegex: true
996
964
  };
997
- finalResult.formattedOutput = formatGrepResults(finalResult);
998
- return finalResult.formattedOutput;
999
- }
1000
- catch (error) {
1001
- throw new Error(`Grep search failed: ${error.message}`);
965
+ const regexRetryResult = await searchRemote(regexRetryParams, contextManager);
966
+ if (regexRetryResult.matches.length > 0) {
967
+ result = regexRetryResult;
968
+ }
969
+ }
970
+ backend = result.backend;
971
+ matchesPerQuery[searchQuery] = result.totalMatches;
972
+ rawTotalMatches += result.totalMatches;
973
+ if (result.truncated) {
974
+ truncated = true;
975
+ truncationReason = result.truncationReason;
976
+ }
977
+ if (result.encodingFallback) {
978
+ encodingFallback = true;
979
+ }
980
+ for (const match of result.matches) {
981
+ const key = `${match.file}:${match.line}:${match.column ?? 0}:${match.match.substring(0, 50)}`;
982
+ const maxMatches = args.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
983
+ if (!seenMatches.has(key) && allMatches.length < maxMatches) {
984
+ seenMatches.add(key);
985
+ allMatches.push(match);
986
+ }
987
+ }
988
+ } catch {
989
+ matchesPerQuery[searchQuery] = 0;
990
+ continue;
1002
991
  }
1003
- },
992
+ }
993
+ const endTime = Date.now();
994
+ const uniqueMatchesCount = allMatches.length;
995
+ const max = args.MaxResults || GrepSearchTool.DEFAULT_MAX_MATCHES;
996
+ const finalResult = {
997
+ matches: allMatches,
998
+ totalMatches: rawTotalMatches,
999
+ uniqueMatchesCount,
1000
+ truncated: truncated || allMatches.length >= max,
1001
+ truncationReason: allMatches.length >= max ? "max_matches" : truncationReason,
1002
+ searchPattern: queries.length > 1 ? `"${query}" (+ individual words)` : query,
1003
+ metadata: {
1004
+ backend,
1005
+ searchDurationMs: endTime - startTime,
1006
+ queriesExecuted,
1007
+ matchesPerQuery,
1008
+ encodingFallback
1009
+ },
1010
+ formattedOutput: ""
1011
+ };
1012
+ finalResult.formattedOutput = formatGrepResults(finalResult);
1013
+ return finalResult.formattedOutput;
1014
+ } catch (error) {
1015
+ throw new Error(`Grep search failed: ${error.message}`);
1016
+ }
1017
+ }
1018
+ };
1019
+ export {
1020
+ GrepSearchTool,
1021
+ grepSearchTool
1004
1022
  };
1005
1023
  //# sourceMappingURL=grep-search.js.map