@travisennis/acai 0.0.3 → 0.0.5

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 (316) hide show
  1. package/README.md +225 -6
  2. package/dist/api/exa/index.d.ts +177 -0
  3. package/dist/api/exa/index.d.ts.map +1 -0
  4. package/dist/api/exa/index.js +439 -0
  5. package/dist/cli.d.ts +3 -2
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/commands/application-log-command.d.ts +1 -0
  8. package/dist/commands/application-log-command.d.ts.map +1 -0
  9. package/dist/commands/application-log-command.js +5 -3
  10. package/dist/commands/clear-command.d.ts +1 -0
  11. package/dist/commands/clear-command.d.ts.map +1 -0
  12. package/dist/commands/clear-command.js +2 -3
  13. package/dist/commands/compact-command.d.ts +1 -0
  14. package/dist/commands/compact-command.d.ts.map +1 -0
  15. package/dist/commands/compact-command.js +1 -1
  16. package/dist/commands/copy-command.d.ts +1 -0
  17. package/dist/commands/copy-command.d.ts.map +1 -0
  18. package/dist/commands/copy-command.js +3 -2
  19. package/dist/commands/edit-command.d.ts +1 -0
  20. package/dist/commands/edit-command.d.ts.map +1 -0
  21. package/dist/commands/edit-command.js +7 -5
  22. package/dist/commands/edit-prompt-command.d.ts +2 -1
  23. package/dist/commands/edit-prompt-command.d.ts.map +1 -0
  24. package/dist/commands/edit-prompt-command.js +15 -7
  25. package/dist/commands/exit-command.d.ts +13 -2
  26. package/dist/commands/exit-command.d.ts.map +1 -0
  27. package/dist/commands/exit-command.js +14 -2
  28. package/dist/commands/files-command.d.ts +1 -0
  29. package/dist/commands/files-command.d.ts.map +1 -0
  30. package/dist/commands/files-command.js +9 -8
  31. package/dist/commands/generate-rules-command.d.ts +1 -0
  32. package/dist/commands/generate-rules-command.d.ts.map +1 -0
  33. package/dist/commands/generate-rules-command.js +4 -3
  34. package/dist/commands/health-command.d.ts +3 -1
  35. package/dist/commands/health-command.d.ts.map +1 -0
  36. package/dist/commands/health-command.js +42 -5
  37. package/dist/commands/help-command.d.ts +1 -0
  38. package/dist/commands/help-command.d.ts.map +1 -0
  39. package/dist/commands/help-command.js +2 -3
  40. package/dist/commands/init-command.d.ts +1 -0
  41. package/dist/commands/init-command.d.ts.map +1 -0
  42. package/dist/commands/init-command.js +1 -2
  43. package/dist/commands/last-log-command.d.ts +1 -0
  44. package/dist/commands/last-log-command.d.ts.map +1 -0
  45. package/dist/commands/last-log-command.js +12 -17
  46. package/dist/commands/list-tools-command.d.ts +3 -0
  47. package/dist/commands/list-tools-command.d.ts.map +1 -0
  48. package/dist/commands/list-tools-command.js +61 -0
  49. package/dist/commands/manager.d.ts +7 -2
  50. package/dist/commands/manager.d.ts.map +1 -0
  51. package/dist/commands/manager.js +43 -6
  52. package/dist/commands/model-command.d.ts +1 -0
  53. package/dist/commands/model-command.d.ts.map +1 -0
  54. package/dist/commands/model-command.js +5 -5
  55. package/dist/commands/paste-command.d.ts +1 -0
  56. package/dist/commands/paste-command.d.ts.map +1 -0
  57. package/dist/commands/paste-command.js +6 -5
  58. package/dist/commands/prompt-command.d.ts +2 -1
  59. package/dist/commands/prompt-command.d.ts.map +1 -0
  60. package/dist/commands/prompt-command.js +62 -8
  61. package/dist/commands/reset-command.d.ts +1 -0
  62. package/dist/commands/reset-command.d.ts.map +1 -0
  63. package/dist/commands/reset-command.js +1 -1
  64. package/dist/commands/rules-command.d.ts +1 -0
  65. package/dist/commands/rules-command.d.ts.map +1 -0
  66. package/dist/commands/rules-command.js +5 -3
  67. package/dist/commands/save-command.d.ts +1 -0
  68. package/dist/commands/save-command.d.ts.map +1 -0
  69. package/dist/commands/save-command.js +1 -1
  70. package/dist/commands/shell-command.d.ts +3 -0
  71. package/dist/commands/shell-command.d.ts.map +1 -0
  72. package/dist/commands/shell-command.js +60 -0
  73. package/dist/commands/types.d.ts +9 -6
  74. package/dist/commands/types.d.ts.map +1 -0
  75. package/dist/commands/usage-command.d.ts +1 -0
  76. package/dist/commands/usage-command.d.ts.map +1 -0
  77. package/dist/commands/usage-command.js +2 -3
  78. package/dist/config.d.ts +22 -34
  79. package/dist/config.d.ts.map +1 -0
  80. package/dist/config.js +61 -15
  81. package/dist/conversation-analyzer.d.ts +2 -1
  82. package/dist/conversation-analyzer.d.ts.map +1 -0
  83. package/dist/dedent.d.ts +1 -0
  84. package/dist/dedent.d.ts.map +1 -0
  85. package/dist/execution/index.d.ts +112 -0
  86. package/dist/execution/index.d.ts.map +1 -0
  87. package/dist/execution/index.js +432 -0
  88. package/dist/formatting.d.ts +2 -13
  89. package/dist/formatting.d.ts.map +1 -0
  90. package/dist/formatting.js +5 -64
  91. package/dist/index.d.ts +1 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +14 -4
  94. package/dist/logger.d.ts +1 -0
  95. package/dist/logger.d.ts.map +1 -0
  96. package/dist/mentions.d.ts +4 -0
  97. package/dist/mentions.d.ts.map +1 -0
  98. package/dist/mentions.js +42 -10
  99. package/dist/messages.d.ts +8 -20
  100. package/dist/messages.d.ts.map +1 -0
  101. package/dist/messages.js +33 -53
  102. package/dist/middleware/audit-message.d.ts +1 -0
  103. package/dist/middleware/audit-message.d.ts.map +1 -0
  104. package/dist/middleware/index.d.ts +1 -0
  105. package/dist/middleware/index.d.ts.map +1 -0
  106. package/dist/middleware/rate-limit.d.ts +1 -0
  107. package/dist/middleware/rate-limit.d.ts.map +1 -0
  108. package/dist/models/ai-config.d.ts +1 -0
  109. package/dist/models/ai-config.d.ts.map +1 -0
  110. package/dist/models/anthropic-provider.d.ts +1 -0
  111. package/dist/models/anthropic-provider.d.ts.map +1 -0
  112. package/dist/models/deepseek-provider.d.ts +1 -0
  113. package/dist/models/deepseek-provider.d.ts.map +1 -0
  114. package/dist/models/google-provider.d.ts +1 -0
  115. package/dist/models/google-provider.d.ts.map +1 -0
  116. package/dist/models/groq-provider.d.ts +20 -0
  117. package/dist/models/groq-provider.d.ts.map +1 -0
  118. package/dist/models/groq-provider.js +31 -0
  119. package/dist/models/manager.d.ts +1 -0
  120. package/dist/models/manager.d.ts.map +1 -0
  121. package/dist/models/openai-provider.d.ts +2 -1
  122. package/dist/models/openai-provider.d.ts.map +1 -0
  123. package/dist/models/openrouter-provider.d.ts +31 -22
  124. package/dist/models/openrouter-provider.d.ts.map +1 -0
  125. package/dist/models/openrouter-provider.js +115 -1
  126. package/dist/models/providers.d.ts +4 -5
  127. package/dist/models/providers.d.ts.map +1 -0
  128. package/dist/models/providers.js +7 -3
  129. package/dist/models/xai-provider.d.ts +1 -0
  130. package/dist/models/xai-provider.d.ts.map +1 -0
  131. package/dist/parsing.d.ts +2 -1
  132. package/dist/parsing.d.ts.map +1 -0
  133. package/dist/prompts/manager.d.ts +14 -2
  134. package/dist/prompts/manager.d.ts.map +1 -0
  135. package/dist/prompts.d.ts +1 -0
  136. package/dist/prompts.d.ts.map +1 -0
  137. package/dist/prompts.js +17 -11
  138. package/dist/repl/display-tool-messages.d.ts +4 -0
  139. package/dist/repl/display-tool-messages.d.ts.map +1 -0
  140. package/dist/repl/display-tool-messages.js +55 -0
  141. package/dist/repl/display-tool-use.d.ts +14 -0
  142. package/dist/repl/display-tool-use.d.ts.map +1 -0
  143. package/dist/repl/display-tool-use.js +63 -0
  144. package/dist/repl/get-prompt-header.d.ts +8 -0
  145. package/dist/repl/get-prompt-header.d.ts.map +1 -0
  146. package/dist/repl/get-prompt-header.js +38 -0
  147. package/dist/repl/tool-call-repair.d.ts +4 -0
  148. package/dist/repl/tool-call-repair.d.ts.map +1 -0
  149. package/dist/repl/tool-call-repair.js +50 -0
  150. package/dist/repl-prompt.d.ts +1 -0
  151. package/dist/repl-prompt.d.ts.map +1 -0
  152. package/dist/repl.d.ts +8 -4
  153. package/dist/repl.d.ts.map +1 -0
  154. package/dist/repl.js +108 -252
  155. package/dist/terminal/ansi-styles.d.ts +77 -0
  156. package/dist/terminal/ansi-styles.d.ts.map +1 -0
  157. package/dist/terminal/ansi-styles.js +215 -0
  158. package/dist/terminal/checkbox-prompt.d.ts +36 -0
  159. package/dist/terminal/checkbox-prompt.d.ts.map +1 -0
  160. package/dist/terminal/checkbox-prompt.js +362 -0
  161. package/dist/terminal/default-theme.d.ts +6 -0
  162. package/dist/terminal/default-theme.d.ts.map +1 -0
  163. package/dist/terminal/default-theme.js +182 -0
  164. package/dist/terminal/east-asian-width.d.ts +8 -0
  165. package/dist/terminal/east-asian-width.d.ts.map +1 -0
  166. package/dist/terminal/east-asian-width.js +409 -0
  167. package/dist/terminal/editor-prompt.d.ts +10 -0
  168. package/dist/terminal/editor-prompt.d.ts.map +1 -0
  169. package/dist/terminal/editor-prompt.js +61 -0
  170. package/dist/terminal/errors.d.ts +19 -0
  171. package/dist/terminal/errors.d.ts.map +1 -0
  172. package/dist/terminal/errors.js +37 -0
  173. package/dist/terminal/formatting.d.ts +1 -11
  174. package/dist/terminal/formatting.d.ts.map +1 -0
  175. package/dist/terminal/formatting.js +4 -20
  176. package/dist/terminal/highlight/index.d.ts +53 -0
  177. package/dist/terminal/highlight/index.d.ts.map +1 -0
  178. package/dist/terminal/highlight/index.js +90 -0
  179. package/dist/terminal/highlight/theme.d.ts +233 -0
  180. package/dist/terminal/highlight/theme.d.ts.map +1 -0
  181. package/dist/terminal/highlight/theme.js +83 -0
  182. package/dist/terminal/index.d.ts +16 -9
  183. package/dist/terminal/index.d.ts.map +1 -0
  184. package/dist/terminal/index.js +42 -126
  185. package/dist/terminal/input-prompt.d.ts +16 -0
  186. package/dist/terminal/input-prompt.d.ts.map +1 -0
  187. package/dist/terminal/input-prompt.js +181 -0
  188. package/dist/terminal/markdown-utils.d.ts +1 -0
  189. package/dist/terminal/markdown-utils.d.ts.map +1 -0
  190. package/dist/terminal/markdown.d.ts +1 -0
  191. package/dist/terminal/markdown.d.ts.map +1 -0
  192. package/dist/terminal/markdown.js +17 -12
  193. package/dist/terminal/search-prompt.d.ts +20 -0
  194. package/dist/terminal/search-prompt.d.ts.map +1 -0
  195. package/dist/terminal/search-prompt.js +279 -0
  196. package/dist/terminal/select-prompt.d.ts +26 -0
  197. package/dist/terminal/select-prompt.d.ts.map +1 -0
  198. package/dist/terminal/select-prompt.js +298 -0
  199. package/dist/terminal/string-width.d.ts +7 -0
  200. package/dist/terminal/string-width.d.ts.map +1 -0
  201. package/dist/terminal/string-width.js +61 -0
  202. package/dist/terminal/strip-ansi.d.ts +2 -0
  203. package/dist/terminal/strip-ansi.d.ts.map +1 -0
  204. package/dist/terminal/strip-ansi.js +20 -0
  205. package/dist/terminal/style.d.ts +191 -0
  206. package/dist/terminal/style.d.ts.map +1 -0
  207. package/dist/terminal/style.js +259 -0
  208. package/dist/terminal/supports-color.d.ts +1 -0
  209. package/dist/terminal/supports-color.d.ts.map +1 -0
  210. package/dist/terminal/supports-hyperlinks.d.ts +1 -3
  211. package/dist/terminal/supports-hyperlinks.d.ts.map +1 -0
  212. package/dist/terminal/supports-hyperlinks.js +1 -1
  213. package/dist/terminal/types.d.ts +1 -37
  214. package/dist/terminal/types.d.ts.map +1 -0
  215. package/dist/terminal/wrap-ansi.d.ts +8 -0
  216. package/dist/terminal/wrap-ansi.d.ts.map +1 -0
  217. package/dist/terminal/wrap-ansi.js +190 -0
  218. package/dist/{token-utils.d.ts → tokens/counter.d.ts} +1 -0
  219. package/dist/tokens/counter.d.ts.map +1 -0
  220. package/dist/{token-utils.js → tokens/counter.js} +1 -1
  221. package/dist/tokens/manage-output.d.ts +34 -0
  222. package/dist/tokens/manage-output.d.ts.map +1 -0
  223. package/dist/tokens/manage-output.js +44 -0
  224. package/dist/{token-tracker.d.ts → tokens/tracker.d.ts} +1 -0
  225. package/dist/tokens/tracker.d.ts.map +1 -0
  226. package/dist/tool-executor.d.ts +28 -0
  227. package/dist/tool-executor.d.ts.map +1 -0
  228. package/dist/tool-executor.js +74 -0
  229. package/dist/tools/agent.d.ts +3 -2
  230. package/dist/tools/agent.d.ts.map +1 -0
  231. package/dist/tools/agent.js +7 -4
  232. package/dist/tools/bash-utils.d.ts +7 -0
  233. package/dist/tools/bash-utils.d.ts.map +1 -0
  234. package/dist/tools/bash-utils.js +212 -0
  235. package/dist/tools/bash.d.ts +9 -7
  236. package/dist/tools/bash.d.ts.map +1 -0
  237. package/dist/tools/bash.js +95 -212
  238. package/dist/tools/code-interpreter.d.ts +1 -0
  239. package/dist/tools/code-interpreter.d.ts.map +1 -0
  240. package/dist/tools/code-interpreter.js +33 -8
  241. package/dist/tools/delete-file.d.ts +5 -3
  242. package/dist/tools/delete-file.d.ts.map +1 -0
  243. package/dist/tools/delete-file.js +47 -33
  244. package/dist/tools/directory-tree.d.ts +10 -1
  245. package/dist/tools/directory-tree.d.ts.map +1 -0
  246. package/dist/tools/directory-tree.js +91 -8
  247. package/dist/tools/dynamic-tool-loader.d.ts +12 -0
  248. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -0
  249. package/dist/tools/dynamic-tool-loader.js +280 -0
  250. package/dist/tools/dynamic-tool-parser.d.ts +20 -0
  251. package/dist/tools/dynamic-tool-parser.d.ts.map +1 -0
  252. package/dist/tools/dynamic-tool-parser.js +21 -0
  253. package/dist/tools/edit-file.d.ts +10 -2
  254. package/dist/tools/edit-file.d.ts.map +1 -0
  255. package/dist/tools/edit-file.js +117 -40
  256. package/dist/tools/file-editing-utils.d.ts +2 -0
  257. package/dist/tools/file-editing-utils.d.ts.map +1 -0
  258. package/dist/tools/file-editing-utils.js +135 -0
  259. package/dist/tools/filesystem-utils.d.ts +6 -21
  260. package/dist/tools/filesystem-utils.d.ts.map +1 -0
  261. package/dist/tools/filesystem-utils.js +96 -148
  262. package/dist/tools/git-utils.d.ts +1 -0
  263. package/dist/tools/git-utils.d.ts.map +1 -0
  264. package/dist/tools/grep.d.ts +5 -3
  265. package/dist/tools/grep.d.ts.map +1 -0
  266. package/dist/tools/grep.js +67 -27
  267. package/dist/tools/index.d.ts +10 -14
  268. package/dist/tools/index.d.ts.map +1 -0
  269. package/dist/tools/index.js +33 -22
  270. package/dist/tools/move-file.d.ts +1 -0
  271. package/dist/tools/move-file.d.ts.map +1 -0
  272. package/dist/tools/move-file.js +12 -5
  273. package/dist/tools/read-file.d.ts +2 -1
  274. package/dist/tools/read-file.d.ts.map +1 -0
  275. package/dist/tools/read-file.js +13 -6
  276. package/dist/tools/read-multiple-files.d.ts +2 -1
  277. package/dist/tools/read-multiple-files.d.ts.map +1 -0
  278. package/dist/tools/read-multiple-files.js +90 -9
  279. package/dist/tools/save-file.d.ts +5 -3
  280. package/dist/tools/save-file.d.ts.map +1 -0
  281. package/dist/tools/save-file.js +64 -36
  282. package/dist/tools/think.d.ts +1 -0
  283. package/dist/tools/think.d.ts.map +1 -0
  284. package/dist/tools/think.js +5 -1
  285. package/dist/tools/types.d.ts +14 -1
  286. package/dist/tools/types.d.ts.map +1 -0
  287. package/dist/tools/web-fetch.d.ts +4 -2
  288. package/dist/tools/web-fetch.d.ts.map +1 -0
  289. package/dist/tools/web-fetch.js +2 -2
  290. package/dist/tools/web-search.d.ts +2 -1
  291. package/dist/tools/web-search.d.ts.map +1 -0
  292. package/dist/tools/web-search.js +46 -11
  293. package/dist/utils/filesystem.d.ts +23 -0
  294. package/dist/utils/filesystem.d.ts.map +1 -0
  295. package/dist/utils/filesystem.js +140 -0
  296. package/dist/utils/filetype-detection.d.ts +3 -0
  297. package/dist/utils/filetype-detection.d.ts.map +1 -0
  298. package/dist/utils/filetype-detection.js +112 -0
  299. package/dist/utils/glob.d.ts +52 -0
  300. package/dist/utils/glob.d.ts.map +1 -0
  301. package/dist/utils/glob.js +376 -0
  302. package/dist/utils/ignore.d.ts +104 -0
  303. package/dist/utils/ignore.d.ts.map +1 -0
  304. package/dist/utils/ignore.js +649 -0
  305. package/dist/utils/process.d.ts +10 -1
  306. package/dist/utils/process.d.ts.map +1 -0
  307. package/dist/utils/process.js +104 -5
  308. package/dist/utils/zod-utils.d.ts +4 -0
  309. package/dist/utils/zod-utils.d.ts.map +1 -0
  310. package/dist/utils/zod-utils.js +7 -0
  311. package/dist/version.d.ts +1 -0
  312. package/dist/version.d.ts.map +1 -0
  313. package/package.json +32 -30
  314. package/dist/tools/command-validation.d.ts +0 -12
  315. package/dist/tools/command-validation.js +0 -113
  316. /package/dist/{token-tracker.js → tokens/tracker.js} +0 -0
@@ -0,0 +1,135 @@
1
+ const LineTrimmedReplacer = function* (content, find) {
2
+ const originalLines = content.split("\n");
3
+ const searchLines = find.split("\n");
4
+ if (searchLines[searchLines.length - 1] === "") {
5
+ searchLines.pop();
6
+ }
7
+ for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
8
+ let matches = true;
9
+ for (let j = 0; j < searchLines.length; j++) {
10
+ const originalTrimmed = originalLines[i + j].trim();
11
+ const searchTrimmed = searchLines[j].trim();
12
+ if (originalTrimmed !== searchTrimmed) {
13
+ matches = false;
14
+ break;
15
+ }
16
+ }
17
+ if (matches) {
18
+ let matchStartIndex = 0;
19
+ for (let k = 0; k < i; k++) {
20
+ matchStartIndex += originalLines[k].length + 1;
21
+ }
22
+ let matchEndIndex = matchStartIndex;
23
+ for (let k = 0; k < searchLines.length; k++) {
24
+ matchEndIndex += originalLines[i + k].length;
25
+ if (k < searchLines.length - 1) {
26
+ matchEndIndex += 1; // Add newline character except for the last line
27
+ }
28
+ }
29
+ yield content.substring(matchStartIndex, matchEndIndex);
30
+ }
31
+ }
32
+ };
33
+ const WhitespaceNormalizedReplacer = function* (content, find) {
34
+ const normalizeWhitespace = (text) => text.replace(/\s+/g, " ").trim();
35
+ const normalizedFind = normalizeWhitespace(find);
36
+ // Handle single line matches
37
+ const lines = content.split("\n");
38
+ for (let i = 0; i < lines.length; i++) {
39
+ const line = lines[i];
40
+ if (typeof line === "undefined") {
41
+ continue;
42
+ }
43
+ if (normalizeWhitespace(line) === normalizedFind) {
44
+ yield line;
45
+ }
46
+ else {
47
+ // Only check for substring matches if the full line doesn't match
48
+ const normalizedLine = normalizeWhitespace(line);
49
+ if (normalizedLine.includes(normalizedFind)) {
50
+ // Find the actual substring in the original line that matches
51
+ const words = find.trim().split(/\s+/);
52
+ if (words.length > 0) {
53
+ const pattern = words
54
+ .map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
55
+ .join("\\s+");
56
+ try {
57
+ const regex = new RegExp(pattern);
58
+ const match = line.match(regex);
59
+ if (match) {
60
+ yield match[0];
61
+ }
62
+ }
63
+ catch (_e) {
64
+ // Invalid regex pattern, skip
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ // Handle multi-line matches
71
+ const findLines = find.split("\n");
72
+ if (findLines.length > 1) {
73
+ for (let i = 0; i <= lines.length - findLines.length; i++) {
74
+ const block = lines.slice(i, i + findLines.length);
75
+ if (normalizeWhitespace(block.join("\n")) === normalizedFind) {
76
+ yield block.join("\n");
77
+ }
78
+ }
79
+ }
80
+ };
81
+ const IndentationFlexibleReplacer = function* (content, find) {
82
+ const removeIndentation = (text) => {
83
+ const lines = text.split("\n");
84
+ const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
85
+ if (nonEmptyLines.length === 0)
86
+ return text;
87
+ const minIndent = Math.min(...nonEmptyLines.map((line) => {
88
+ const match = line.match(/^(\s*)/);
89
+ return match ? match[1].length : 0;
90
+ }));
91
+ return lines
92
+ .map((line) => (line.trim().length === 0 ? line : line.slice(minIndent)))
93
+ .join("\n");
94
+ };
95
+ const normalizedFind = removeIndentation(find);
96
+ const contentLines = content.split("\n");
97
+ const findLines = find.split("\n");
98
+ for (let i = 0; i <= contentLines.length - findLines.length; i++) {
99
+ const block = contentLines.slice(i, i + findLines.length).join("\n");
100
+ if (removeIndentation(block) === normalizedFind) {
101
+ yield block;
102
+ }
103
+ }
104
+ };
105
+ export function replace(content, oldString, newString, replaceAll = false) {
106
+ if (oldString === newString) {
107
+ throw new Error("oldString and newString must be different");
108
+ }
109
+ let notFound = true;
110
+ for (const replacer of [
111
+ LineTrimmedReplacer,
112
+ WhitespaceNormalizedReplacer,
113
+ IndentationFlexibleReplacer,
114
+ ]) {
115
+ for (const search of replacer(content, oldString)) {
116
+ const index = content.indexOf(search);
117
+ if (index === -1)
118
+ continue;
119
+ notFound = false;
120
+ if (replaceAll) {
121
+ return content.replaceAll(search, newString);
122
+ }
123
+ const lastIndex = content.lastIndexOf(search);
124
+ if (index !== lastIndex)
125
+ continue;
126
+ return (content.substring(0, index) +
127
+ newString +
128
+ content.substring(index + search.length));
129
+ }
130
+ }
131
+ if (notFound) {
132
+ throw new Error("oldString not found in content");
133
+ }
134
+ throw new Error("oldString found multiple times and requires more code context to uniquely identify the intended match");
135
+ }
@@ -1,22 +1,7 @@
1
- import type { TokenCounter } from "../token-utils.ts";
2
- export declare function normalizePath(p: string): string;
3
1
  export declare function joinWorkingDir(userPath: string, workingDir: string): string;
4
- export declare function expandHome(filepath: string): string;
5
- export declare function validatePath(requestedPath: string, allowedDirectory: string): Promise<string>;
6
- export interface FileEdit {
7
- oldText: string;
8
- newText: string;
9
- }
10
- export declare function applyFileEdits(filePath: string, edits: FileEdit[], dryRun?: boolean): Promise<string>;
11
- /**
12
- * Generates a string representation of a directory tree starting from the given path.
13
- * @param dirPath - The path of the directory to generate the tree for.
14
- * @returns A Promise that resolves to a string representation of the directory tree.
15
- */
16
- export declare function directoryTree(dirPath: string): Promise<string>;
17
- export declare function readFileAndCountTokens(filePath: string, workingDir: string, allowedDirectory: string, tokenCounter: TokenCounter, maxTokens: number): Promise<{
18
- path: string;
19
- content: string | null;
20
- tokenCount: number;
21
- error: string | null;
22
- }>;
2
+ export declare function isPathWithinBaseDir(requestedPath: string, baseDir: string): boolean;
3
+ export declare function validatePath(requestedPath: string, allowedDirectory: string, options?: {
4
+ requireExistence?: boolean;
5
+ abortSignal?: AbortSignal;
6
+ }): Promise<string>;
7
+ //# sourceMappingURL=filesystem-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem-utils.d.ts","sourceRoot":"","sources":["../../source/tools/filesystem-utils.ts"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK3E;AAUD,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAsBT;AAGD,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAO,GACtE,OAAO,CAAC,MAAM,CAAC,CA0GjB"}
@@ -1,10 +1,9 @@
1
+ import { realpathSync } from "node:fs";
1
2
  import fs from "node:fs/promises";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
4
- import { createTwoFilesPatch } from "diff";
5
- import ignore from "ignore";
6
5
  // Normalize all paths consistently
7
- export function normalizePath(p) {
6
+ function normalizePath(p) {
8
7
  return path.normalize(p);
9
8
  }
10
9
  // Handle path joining with working directory
@@ -14,178 +13,127 @@ export function joinWorkingDir(userPath, workingDir) {
14
13
  }
15
14
  return path.normalize(path.join(workingDir, userPath));
16
15
  }
17
- export function expandHome(filepath) {
16
+ function expandHome(filepath) {
18
17
  if (filepath.startsWith("~/") || filepath === "~") {
19
18
  return path.join(os.homedir(), filepath.slice(1));
20
19
  }
21
20
  return filepath;
22
21
  }
22
+ // Ensure path is within base directory (handles '.', relative paths, and symlinks)
23
+ export function isPathWithinBaseDir(requestedPath, baseDir) {
24
+ const baseAbs = path.resolve(baseDir);
25
+ let baseReal = baseAbs;
26
+ try {
27
+ baseReal = realpathSync(baseAbs);
28
+ }
29
+ catch {
30
+ // If baseDir doesn't exist, fall back to resolved path
31
+ }
32
+ const abs = path.isAbsolute(requestedPath)
33
+ ? path.resolve(requestedPath)
34
+ : path.resolve(baseReal, requestedPath);
35
+ let target = abs;
36
+ try {
37
+ target = realpathSync(abs);
38
+ }
39
+ catch {
40
+ // If target doesn't fully exist, validate against intended path
41
+ }
42
+ const rel = path.relative(baseReal, target);
43
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
44
+ }
23
45
  // Security utilities
24
- export async function validatePath(requestedPath, allowedDirectory) {
46
+ export async function validatePath(requestedPath, allowedDirectory, options = {}) {
47
+ const { requireExistence = true, abortSignal } = options;
48
+ if (abortSignal?.aborted) {
49
+ throw new Error("Path validation aborted");
50
+ }
25
51
  const expandedPath = expandHome(requestedPath);
26
52
  const absolute = path.isAbsolute(expandedPath)
27
53
  ? path.resolve(expandedPath)
28
54
  : path.resolve(process.cwd(), expandedPath);
29
55
  const normalizedRequested = normalizePath(absolute);
30
- // Check if path is within allowed directories
31
- const isAllowed = normalizedRequested.startsWith(allowedDirectory);
32
- if (!isAllowed) {
56
+ let normalizedAllowed = normalizePath(path.resolve(allowedDirectory));
57
+ // Try to resolve real path for allowedDirectory when it exists to handle symlinked roots
58
+ try {
59
+ const stats = await fs.stat(normalizedAllowed);
60
+ if (stats.isDirectory()) {
61
+ const allowedReal = await fs.realpath(normalizedAllowed);
62
+ normalizedAllowed = normalizePath(allowedReal);
63
+ }
64
+ }
65
+ catch (_err) {
66
+ // If allowedDirectory doesn't exist, keep normalizedAllowed as-is
67
+ }
68
+ // Helper to check if a path is within the allowed directory using path.relative
69
+ const isWithinAllowed = (targetPath) => {
70
+ const rel = path.relative(normalizedAllowed, targetPath);
71
+ // Allow the allowed directory itself (rel === "") and any descendant paths
72
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
73
+ };
74
+ // Check intended path is within allowed directory
75
+ if (!isWithinAllowed(normalizedRequested)) {
33
76
  throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectory}`);
34
77
  }
35
- // Handle symlinks by checking their real path
78
+ let validatedPath;
79
+ // Try to resolve real path for existing targets to handle symlinks safely
36
80
  try {
37
81
  const realPath = await fs.realpath(absolute);
38
82
  const normalizedReal = normalizePath(realPath);
39
- const isRealPathAllowed = normalizedReal.startsWith(allowedDirectory);
40
- if (!isRealPathAllowed) {
83
+ if (!isWithinAllowed(normalizedReal)) {
41
84
  throw new Error("Access denied - symlink target outside allowed directories");
42
85
  }
43
- return realPath;
86
+ validatedPath = realPath;
44
87
  }
45
88
  catch (_error) {
46
- // For new files that don't exist yet, verify parent directory
47
- const parentDir = path.dirname(absolute);
48
- try {
49
- const realParentPath = await fs.realpath(parentDir);
50
- const normalizedParent = normalizePath(realParentPath);
51
- const isParentAllowed = normalizedParent.startsWith(allowedDirectory);
52
- if (!isParentAllowed) {
53
- throw new Error("Access denied - parent directory outside allowed directories");
89
+ // For new files or paths where some directories don't exist yet:
90
+ // Walk up to the nearest existing ancestor directory and validate it.
91
+ let current = path.dirname(absolute);
92
+ let foundValidAncestor = false;
93
+ while (true) {
94
+ try {
95
+ const stat = await fs.stat(current);
96
+ if (!stat.isDirectory()) {
97
+ throw new Error(`Nearest existing ancestor is not a directory: ${current}`);
98
+ }
99
+ const realAncestor = await fs.realpath(current);
100
+ const normalizedAncestor = normalizePath(realAncestor);
101
+ if (!isWithinAllowed(normalizedAncestor)) {
102
+ throw new Error("Access denied - ancestor directory resolves outside allowed directories");
103
+ }
104
+ // Ancestor is within allowed; allow creation below it.
105
+ foundValidAncestor = true;
106
+ break;
107
+ }
108
+ catch (_err) {
109
+ // If we reached the filesystem root, break to fallback check
110
+ const parent = path.dirname(current);
111
+ if (parent === current) {
112
+ break;
113
+ }
114
+ current = parent;
54
115
  }
55
- return absolute;
56
- }
57
- catch {
58
- throw new Error(`Parent directory does not exist: ${parentDir}`);
59
- }
60
- }
61
- }
62
- // file editing and diffing utilities
63
- function normalizeLineEndings(text) {
64
- return text.replace(/\r\n/g, "\n");
65
- }
66
- function createUnifiedDiff(originalContent, newContent, filepath = "file") {
67
- // Ensure consistent line endings for diff
68
- const normalizedOriginal = normalizeLineEndings(originalContent);
69
- const normalizedNew = normalizeLineEndings(newContent);
70
- return createTwoFilesPatch(filepath, filepath, normalizedOriginal, normalizedNew, "original", "modified");
71
- }
72
- export async function applyFileEdits(filePath, edits, dryRun = false) {
73
- // Read file content literally
74
- const originalContent = await fs.readFile(filePath, "utf-8");
75
- if (edits.find((edit) => edit.oldText.length === 0)) {
76
- throw new Error("Invalid oldText in edit. The value of oldText must be at least one character");
77
- }
78
- // Apply edits sequentially
79
- let modifiedContent = originalContent;
80
- for (const edit of edits) {
81
- const { oldText, newText } = edit; // Use literal oldText and newText
82
- const normalizedContent = normalizeLineEndings(modifiedContent);
83
- const normalizedOldText = normalizeLineEndings(oldText);
84
- if (normalizedContent.includes(normalizedOldText)) {
85
- modifiedContent = normalizedContent.replace(normalizedOldText, newText);
86
116
  }
87
- else {
88
- // If literal match is not found, throw an error.
89
- // The previous complex fallback logic is removed to ensure literal matching.
90
- throw new Error(`Could not find literal match for edit:\n${edit.oldText}`);
117
+ if (!foundValidAncestor) {
118
+ // Rely on intended path check
91
119
  }
120
+ validatedPath = absolute;
92
121
  }
93
- // Create unified diff (createUnifiedDiff normalizes line endings internally for diffing)
94
- const diff = createUnifiedDiff(originalContent, modifiedContent, filePath);
95
- // Format diff with appropriate number of backticks
96
- let numBackticks = 3;
97
- while (diff.includes("`".repeat(numBackticks))) {
98
- numBackticks++;
99
- }
100
- const formattedDiff = `${"`".repeat(numBackticks)}diff\n${diff}${"`".repeat(numBackticks)}\n\n`;
101
- if (!dryRun) {
102
- // Write the modified content (which has literal newlines from newText, and preserves original newlines not part of oldText/newText)
103
- await fs.writeFile(filePath, modifiedContent, "utf-8");
104
- }
105
- return formattedDiff;
106
- }
107
- /**
108
- * Generates the indentation string for a given level in the directory tree.
109
- * @param level - The current level in the directory tree.
110
- * @param isLast - Indicates if the current item is the last in its parent directory.
111
- * @returns The indentation string for the current level.
112
- */
113
- function getIndent(level, isLast) {
114
- const indent = "│ ".repeat(level - 1);
115
- return level === 0 ? "" : `${indent}${isLast ? "└── " : "├── "}`;
116
- }
117
- /**
118
- * Recursively generates a string representation of a directory tree.
119
- * @param dirPath - The path of the directory to generate the tree for.
120
- * @param level - The current level in the directory tree (default: 1).
121
- * @returns A Promise that resolves to a string representation of the directory tree.
122
- * @throws Will log an error if there's an issue reading the directory.
123
- */
124
- async function generateDirectoryTree(dirPath, ig, level = 1) {
125
- const name = path.basename(dirPath);
126
- let output = `${getIndent(level, false)}${name}\n`;
127
- const items = await fs.readdir(dirPath);
128
- const filteredItems = ig.filter(items);
129
- for (let i = 0; i < filteredItems.length; i++) {
130
- const item = filteredItems[i] ?? "";
131
- const itemPath = path.join(dirPath, item);
132
- const isLast = i === items.length - 1;
133
- const stats = await fs.stat(itemPath);
134
- if (stats.isDirectory()) {
135
- output += await generateDirectoryTree(itemPath, ig, level + 1);
122
+ // Now, if requireExistence, check if the path exists
123
+ if (requireExistence) {
124
+ if (abortSignal?.aborted) {
125
+ throw new Error("Path validation aborted during existence check");
136
126
  }
137
- else {
138
- output += `${getIndent(level + 1, isLast)}${item}\n`;
139
- }
140
- }
141
- return output;
142
- }
143
- /**
144
- * Generates a string representation of a directory tree starting from the given path.
145
- * @param dirPath - The path of the directory to generate the tree for.
146
- * @returns A Promise that resolves to a string representation of the directory tree.
147
- */
148
- export async function directoryTree(dirPath) {
149
- let ig;
150
- try {
151
- const ignoreFile = await fs.readFile(path.join(process.cwd(), ".gitignore"));
152
- ig = ignore().add(ignoreFile.toString()).add(".git");
153
- }
154
- catch (_error) {
155
- // If .gitignore doesn't exist, create basic ignore with just .git
156
- ig = ignore().add(".git");
157
- }
158
- return (await generateDirectoryTree(dirPath, ig)).trim();
159
- }
160
- export async function readFileAndCountTokens(filePath, workingDir, allowedDirectory, tokenCounter, maxTokens) {
161
- try {
162
- const validPath = await validatePath(joinWorkingDir(filePath, workingDir), allowedDirectory);
163
- const content = await fs.readFile(validPath, "utf-8");
164
- let tokenCount = 0;
165
127
  try {
166
- tokenCount = tokenCounter.count(content);
128
+ await fs.stat(validatedPath);
167
129
  }
168
- catch (tokenError) {
169
- console.error("Error calculating token count:", tokenError);
170
- // Handle token calculation error if needed
130
+ catch (err) {
131
+ const error = err;
132
+ if (error.code === "ENOENT") {
133
+ throw new Error(`The specified path does not exist: ${requestedPath} (${validatedPath})`);
134
+ }
135
+ throw error;
171
136
  }
172
- const maxTokenMessage = `File content (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}). Use readFile with startLine/lineCount or grepFiles for targeted access.`;
173
- const finalContent = tokenCount > maxTokens ? maxTokenMessage : content;
174
- const actualTokenCount = tokenCount > maxTokens ? 0 : tokenCount; // Don't count tokens for skipped files
175
- return {
176
- path: filePath,
177
- content: finalContent,
178
- tokenCount: actualTokenCount,
179
- error: null,
180
- };
181
- }
182
- catch (error) {
183
- const errorMessage = error instanceof Error ? error.message : String(error);
184
- return {
185
- path: filePath,
186
- content: null,
187
- tokenCount: 0,
188
- error: errorMessage,
189
- };
190
137
  }
138
+ return validatedPath;
191
139
  }
@@ -18,3 +18,4 @@ export declare function hasUncommittedChanges(): Promise<boolean>;
18
18
  * Get the current git branch name
19
19
  */
20
20
  export declare function getCurrentBranch(): Promise<string | null>;
21
+ //# sourceMappingURL=git-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../../source/tools/git-utils.ts"],"names":[],"mappings":"AAIA,wBAAsB,WAAW;;;;GAyDhC;AAED,wBAAsB,YAAY;;;;;GAmCjC;AAED,eAAO,MAAM,cAAc,QAAqB,OAAO,CAAC,OAAO,CAS7D,CAAC;AAEH;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAgB9D;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsB/D"}
@@ -1,13 +1,15 @@
1
+ import type { TokenCounter } from "../tokens/counter.ts";
1
2
  import type { SendData } from "./types.ts";
2
3
  export declare const GrepTool: {
3
4
  name: "grepFiles";
4
5
  };
5
- export declare const createGrepTool: (options?: {
6
+ export declare const createGrepTool: (options: {
6
7
  sendData?: SendData | undefined;
8
+ tokenCounter: TokenCounter;
7
9
  }) => {
8
10
  grepFiles: import("ai").Tool<{
9
- path: string;
10
11
  pattern: string;
12
+ path: string;
11
13
  recursive: boolean | null;
12
14
  ignoreCase: boolean | null;
13
15
  filePattern: string | null;
@@ -33,5 +35,5 @@ interface GrepOptions {
33
35
  * @returns The result of the grep command
34
36
  */
35
37
  export declare function buildGrepCommand(pattern: string, path: string, options?: GrepOptions): string;
36
- export declare function grepFiles(pattern: string, path: string, options?: GrepOptions): string;
37
38
  export {};
39
+ //# sourceMappingURL=grep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../source/tools/grep.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS;IACtC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,YAAY,EAAE,YAAY,CAAC;CAC5B;;;;;;;;;;;CA0KA,CAAC;AAEF,UAAU,WAAW;IACnB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B;AAoDD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,CAmDR"}
@@ -1,50 +1,60 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { inspect } from "node:util";
3
3
  import { tool } from "ai";
4
- import chalk from "chalk";
5
4
  import { z } from "zod";
5
+ import { config } from "../config.js";
6
+ import style from "../terminal/style.js";
7
+ import { manageOutput } from "../tokens/manage-output.js";
6
8
  export const GrepTool = {
7
9
  name: "grepFiles",
8
10
  };
9
- export const createGrepTool = (options = {}) => {
10
- const { sendData } = options;
11
+ export const createGrepTool = (options) => {
12
+ const { sendData, tokenCounter } = options;
11
13
  return {
12
14
  [GrepTool.name]: tool({
13
- description: "Search files for patterns using ripgrep",
15
+ description: `Search files for patterns using ripgrep (rg). Uses glob patterns for file filtering (e.g., "*.ts", "**/*.test.ts"). Auto-detects unbalanced regex patterns and falls back to fixed-string search for safety.`,
14
16
  inputSchema: z.object({
15
- pattern: z.string().describe("The regex pattern to search for"),
17
+ pattern: z
18
+ .string()
19
+ .describe("The search pattern (regex by default, or fixed-string if literal=true or auto-detected as unbalanced)"),
16
20
  path: z.string().describe("The path to search in"),
17
- recursive: z
21
+ recursive: z.coerce
18
22
  .boolean()
19
23
  .nullable()
20
- .describe("Pass null to use the default (true, search recursively)."),
21
- ignoreCase: z
24
+ .describe("Search recursively. (default: true))"),
25
+ ignoreCase: z.coerce
22
26
  .boolean()
23
27
  .nullable()
24
- .describe("Pass null to use the default (false, case-sensitive search)."),
28
+ .describe("Use case-sensitive search. (default: false)"),
25
29
  filePattern: z
26
30
  .string()
27
31
  .nullable()
28
- .describe("Pass null if no file pattern filter is needed."),
29
- contextLines: z
32
+ .describe("Glob pattern to filter files (e.g., '*.ts', '**/*.test.js'). (Default: no filtering)"),
33
+ contextLines: z.coerce
30
34
  .number()
31
35
  .nullable()
32
- .describe("Pass null if no context lines are needed."),
33
- searchIgnored: z
36
+ .describe("The number of context lines needed in search results. (Default: 0)"),
37
+ searchIgnored: z.coerce
34
38
  .boolean()
35
39
  .nullable()
36
- .describe("Pass null to use the default (false, don't search ignored files)."),
37
- literal: z
40
+ .describe("Search ignored files. (Default: false)"),
41
+ literal: z.coerce
38
42
  .boolean()
39
43
  .nullable()
40
- .describe("Pass true to search as a fixed string (no regex). Pass null to auto-detect."),
44
+ .describe("Pass true for fixed-string search (-F), false for regex, (Default: auto-detects unbalanced patterns like mismatched parentheses/brackets.)"),
41
45
  }),
42
- execute: ({ pattern, path, recursive, ignoreCase, filePattern, contextLines, searchIgnored, literal, }, { toolCallId }) => {
46
+ execute: async ({ pattern, path, recursive, ignoreCase, filePattern, contextLines, searchIgnored, literal, }, { toolCallId, abortSignal }) => {
47
+ // Check if execution has been aborted
48
+ if (abortSignal?.aborted) {
49
+ throw new Error("Grep search aborted");
50
+ }
43
51
  try {
52
+ // grok doesn't follow my instructions
53
+ const safeFilePattern = filePattern === "null" ? null : filePattern;
44
54
  sendData?.({
45
55
  event: "tool-init",
46
56
  id: toolCallId,
47
- data: `Searching codebase for "${chalk.cyan(inspect(pattern))}" in ${chalk.cyan(path)}`,
57
+ data: `Searching codebase for ${style.cyan(inspect(pattern))}${safeFilePattern ? ` with file pattern ${style.cyan(safeFilePattern)}` : ""} in ${style.cyan(path)}`,
48
58
  });
49
59
  // Normalize literal option: if null => auto-detect using heuristic
50
60
  let effectiveLiteral = null;
@@ -75,17 +85,32 @@ export const createGrepTool = (options = {}) => {
75
85
  effectiveLiteral = false;
76
86
  }
77
87
  }
78
- const result = grepFiles(pattern, path, {
88
+ const rawResult = grepFiles(pattern, path, {
79
89
  recursive,
80
90
  ignoreCase,
81
- filePattern,
91
+ filePattern: safeFilePattern,
82
92
  contextLines,
83
93
  searchIgnored,
84
94
  literal: effectiveLiteral,
85
95
  });
86
- const matchCount = result === "No matches found."
87
- ? 0
88
- : result
96
+ const maxTokens = (await config.readProjectConfig()).tools.maxTokens;
97
+ const managed = manageOutput(rawResult, {
98
+ tokenCounter,
99
+ threshold: maxTokens,
100
+ });
101
+ if (managed.truncated) {
102
+ sendData?.({
103
+ event: "tool-update",
104
+ id: toolCallId,
105
+ data: { primary: managed.warning },
106
+ });
107
+ }
108
+ // Extract and filter matches from the content
109
+ const extractMatches = (content) => {
110
+ if (content === "No matches found.") {
111
+ return [];
112
+ }
113
+ return content
89
114
  .trim()
90
115
  .split("\n")
91
116
  .filter((line) => {
@@ -93,13 +118,28 @@ export const createGrepTool = (options = {}) => {
93
118
  return false;
94
119
  }
95
120
  return /^(.+?):(\d+):(.*)$/.test(line);
96
- }).length;
121
+ });
122
+ };
123
+ const matches = extractMatches(managed.content);
124
+ const matchCount = matches.length;
125
+ // Show the last 10 matches as a preview
126
+ if (matchCount > 0) {
127
+ const previewMatches = matches.slice(-10); // Get last 10 matches
128
+ sendData?.({
129
+ event: "tool-update",
130
+ id: toolCallId,
131
+ data: {
132
+ primary: `Last ${previewMatches.length} matches:`,
133
+ secondary: previewMatches,
134
+ },
135
+ });
136
+ }
97
137
  sendData?.({
98
138
  event: "tool-completion",
99
139
  id: toolCallId,
100
- data: `Found ${chalk.cyan(matchCount)} matches.`,
140
+ data: `Found ${style.cyan(matchCount)} matches. (${managed.tokenCount} tokens)`,
101
141
  });
102
- return Promise.resolve(result);
142
+ return Promise.resolve(managed.content);
103
143
  }
104
144
  catch (error) {
105
145
  sendData?.({
@@ -209,7 +249,7 @@ export function buildGrepCommand(pattern, path, options = {}) {
209
249
  command += ` ${path}`;
210
250
  return command;
211
251
  }
212
- export function grepFiles(pattern, path, options = {}) {
252
+ function grepFiles(pattern, path, options = {}) {
213
253
  try {
214
254
  const command = buildGrepCommand(pattern, path, options);
215
255
  const result = execSync(command, { encoding: "utf-8" });