@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.
- package/README.md +225 -6
- package/dist/api/exa/index.d.ts +177 -0
- package/dist/api/exa/index.d.ts.map +1 -0
- package/dist/api/exa/index.js +439 -0
- package/dist/cli.d.ts +3 -2
- package/dist/cli.d.ts.map +1 -0
- package/dist/commands/application-log-command.d.ts +1 -0
- package/dist/commands/application-log-command.d.ts.map +1 -0
- package/dist/commands/application-log-command.js +5 -3
- package/dist/commands/clear-command.d.ts +1 -0
- package/dist/commands/clear-command.d.ts.map +1 -0
- package/dist/commands/clear-command.js +2 -3
- package/dist/commands/compact-command.d.ts +1 -0
- package/dist/commands/compact-command.d.ts.map +1 -0
- package/dist/commands/compact-command.js +1 -1
- package/dist/commands/copy-command.d.ts +1 -0
- package/dist/commands/copy-command.d.ts.map +1 -0
- package/dist/commands/copy-command.js +3 -2
- package/dist/commands/edit-command.d.ts +1 -0
- package/dist/commands/edit-command.d.ts.map +1 -0
- package/dist/commands/edit-command.js +7 -5
- package/dist/commands/edit-prompt-command.d.ts +2 -1
- package/dist/commands/edit-prompt-command.d.ts.map +1 -0
- package/dist/commands/edit-prompt-command.js +15 -7
- package/dist/commands/exit-command.d.ts +13 -2
- package/dist/commands/exit-command.d.ts.map +1 -0
- package/dist/commands/exit-command.js +14 -2
- package/dist/commands/files-command.d.ts +1 -0
- package/dist/commands/files-command.d.ts.map +1 -0
- package/dist/commands/files-command.js +9 -8
- package/dist/commands/generate-rules-command.d.ts +1 -0
- package/dist/commands/generate-rules-command.d.ts.map +1 -0
- package/dist/commands/generate-rules-command.js +4 -3
- package/dist/commands/health-command.d.ts +3 -1
- package/dist/commands/health-command.d.ts.map +1 -0
- package/dist/commands/health-command.js +42 -5
- package/dist/commands/help-command.d.ts +1 -0
- package/dist/commands/help-command.d.ts.map +1 -0
- package/dist/commands/help-command.js +2 -3
- package/dist/commands/init-command.d.ts +1 -0
- package/dist/commands/init-command.d.ts.map +1 -0
- package/dist/commands/init-command.js +1 -2
- package/dist/commands/last-log-command.d.ts +1 -0
- package/dist/commands/last-log-command.d.ts.map +1 -0
- package/dist/commands/last-log-command.js +12 -17
- package/dist/commands/list-tools-command.d.ts +3 -0
- package/dist/commands/list-tools-command.d.ts.map +1 -0
- package/dist/commands/list-tools-command.js +61 -0
- package/dist/commands/manager.d.ts +7 -2
- package/dist/commands/manager.d.ts.map +1 -0
- package/dist/commands/manager.js +43 -6
- package/dist/commands/model-command.d.ts +1 -0
- package/dist/commands/model-command.d.ts.map +1 -0
- package/dist/commands/model-command.js +5 -5
- package/dist/commands/paste-command.d.ts +1 -0
- package/dist/commands/paste-command.d.ts.map +1 -0
- package/dist/commands/paste-command.js +6 -5
- package/dist/commands/prompt-command.d.ts +2 -1
- package/dist/commands/prompt-command.d.ts.map +1 -0
- package/dist/commands/prompt-command.js +62 -8
- package/dist/commands/reset-command.d.ts +1 -0
- package/dist/commands/reset-command.d.ts.map +1 -0
- package/dist/commands/reset-command.js +1 -1
- package/dist/commands/rules-command.d.ts +1 -0
- package/dist/commands/rules-command.d.ts.map +1 -0
- package/dist/commands/rules-command.js +5 -3
- package/dist/commands/save-command.d.ts +1 -0
- package/dist/commands/save-command.d.ts.map +1 -0
- package/dist/commands/save-command.js +1 -1
- package/dist/commands/shell-command.d.ts +3 -0
- package/dist/commands/shell-command.d.ts.map +1 -0
- package/dist/commands/shell-command.js +60 -0
- package/dist/commands/types.d.ts +9 -6
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/usage-command.d.ts +1 -0
- package/dist/commands/usage-command.d.ts.map +1 -0
- package/dist/commands/usage-command.js +2 -3
- package/dist/config.d.ts +22 -34
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +61 -15
- package/dist/conversation-analyzer.d.ts +2 -1
- package/dist/conversation-analyzer.d.ts.map +1 -0
- package/dist/dedent.d.ts +1 -0
- package/dist/dedent.d.ts.map +1 -0
- package/dist/execution/index.d.ts +112 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +432 -0
- package/dist/formatting.d.ts +2 -13
- package/dist/formatting.d.ts.map +1 -0
- package/dist/formatting.js +5 -64
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -4
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/mentions.d.ts +4 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +42 -10
- package/dist/messages.d.ts +8 -20
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +33 -53
- package/dist/middleware/audit-message.d.ts +1 -0
- package/dist/middleware/audit-message.d.ts.map +1 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/rate-limit.d.ts +1 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/models/ai-config.d.ts +1 -0
- package/dist/models/ai-config.d.ts.map +1 -0
- package/dist/models/anthropic-provider.d.ts +1 -0
- package/dist/models/anthropic-provider.d.ts.map +1 -0
- package/dist/models/deepseek-provider.d.ts +1 -0
- package/dist/models/deepseek-provider.d.ts.map +1 -0
- package/dist/models/google-provider.d.ts +1 -0
- package/dist/models/google-provider.d.ts.map +1 -0
- package/dist/models/groq-provider.d.ts +20 -0
- package/dist/models/groq-provider.d.ts.map +1 -0
- package/dist/models/groq-provider.js +31 -0
- package/dist/models/manager.d.ts +1 -0
- package/dist/models/manager.d.ts.map +1 -0
- package/dist/models/openai-provider.d.ts +2 -1
- package/dist/models/openai-provider.d.ts.map +1 -0
- package/dist/models/openrouter-provider.d.ts +31 -22
- package/dist/models/openrouter-provider.d.ts.map +1 -0
- package/dist/models/openrouter-provider.js +115 -1
- package/dist/models/providers.d.ts +4 -5
- package/dist/models/providers.d.ts.map +1 -0
- package/dist/models/providers.js +7 -3
- package/dist/models/xai-provider.d.ts +1 -0
- package/dist/models/xai-provider.d.ts.map +1 -0
- package/dist/parsing.d.ts +2 -1
- package/dist/parsing.d.ts.map +1 -0
- package/dist/prompts/manager.d.ts +14 -2
- package/dist/prompts/manager.d.ts.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +17 -11
- package/dist/repl/display-tool-messages.d.ts +4 -0
- package/dist/repl/display-tool-messages.d.ts.map +1 -0
- package/dist/repl/display-tool-messages.js +55 -0
- package/dist/repl/display-tool-use.d.ts +14 -0
- package/dist/repl/display-tool-use.d.ts.map +1 -0
- package/dist/repl/display-tool-use.js +63 -0
- package/dist/repl/get-prompt-header.d.ts +8 -0
- package/dist/repl/get-prompt-header.d.ts.map +1 -0
- package/dist/repl/get-prompt-header.js +38 -0
- package/dist/repl/tool-call-repair.d.ts +4 -0
- package/dist/repl/tool-call-repair.d.ts.map +1 -0
- package/dist/repl/tool-call-repair.js +50 -0
- package/dist/repl-prompt.d.ts +1 -0
- package/dist/repl-prompt.d.ts.map +1 -0
- package/dist/repl.d.ts +8 -4
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +108 -252
- package/dist/terminal/ansi-styles.d.ts +77 -0
- package/dist/terminal/ansi-styles.d.ts.map +1 -0
- package/dist/terminal/ansi-styles.js +215 -0
- package/dist/terminal/checkbox-prompt.d.ts +36 -0
- package/dist/terminal/checkbox-prompt.d.ts.map +1 -0
- package/dist/terminal/checkbox-prompt.js +362 -0
- package/dist/terminal/default-theme.d.ts +6 -0
- package/dist/terminal/default-theme.d.ts.map +1 -0
- package/dist/terminal/default-theme.js +182 -0
- package/dist/terminal/east-asian-width.d.ts +8 -0
- package/dist/terminal/east-asian-width.d.ts.map +1 -0
- package/dist/terminal/east-asian-width.js +409 -0
- package/dist/terminal/editor-prompt.d.ts +10 -0
- package/dist/terminal/editor-prompt.d.ts.map +1 -0
- package/dist/terminal/editor-prompt.js +61 -0
- package/dist/terminal/errors.d.ts +19 -0
- package/dist/terminal/errors.d.ts.map +1 -0
- package/dist/terminal/errors.js +37 -0
- package/dist/terminal/formatting.d.ts +1 -11
- package/dist/terminal/formatting.d.ts.map +1 -0
- package/dist/terminal/formatting.js +4 -20
- package/dist/terminal/highlight/index.d.ts +53 -0
- package/dist/terminal/highlight/index.d.ts.map +1 -0
- package/dist/terminal/highlight/index.js +90 -0
- package/dist/terminal/highlight/theme.d.ts +233 -0
- package/dist/terminal/highlight/theme.d.ts.map +1 -0
- package/dist/terminal/highlight/theme.js +83 -0
- package/dist/terminal/index.d.ts +16 -9
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +42 -126
- package/dist/terminal/input-prompt.d.ts +16 -0
- package/dist/terminal/input-prompt.d.ts.map +1 -0
- package/dist/terminal/input-prompt.js +181 -0
- package/dist/terminal/markdown-utils.d.ts +1 -0
- package/dist/terminal/markdown-utils.d.ts.map +1 -0
- package/dist/terminal/markdown.d.ts +1 -0
- package/dist/terminal/markdown.d.ts.map +1 -0
- package/dist/terminal/markdown.js +17 -12
- package/dist/terminal/search-prompt.d.ts +20 -0
- package/dist/terminal/search-prompt.d.ts.map +1 -0
- package/dist/terminal/search-prompt.js +279 -0
- package/dist/terminal/select-prompt.d.ts +26 -0
- package/dist/terminal/select-prompt.d.ts.map +1 -0
- package/dist/terminal/select-prompt.js +298 -0
- package/dist/terminal/string-width.d.ts +7 -0
- package/dist/terminal/string-width.d.ts.map +1 -0
- package/dist/terminal/string-width.js +61 -0
- package/dist/terminal/strip-ansi.d.ts +2 -0
- package/dist/terminal/strip-ansi.d.ts.map +1 -0
- package/dist/terminal/strip-ansi.js +20 -0
- package/dist/terminal/style.d.ts +191 -0
- package/dist/terminal/style.d.ts.map +1 -0
- package/dist/terminal/style.js +259 -0
- package/dist/terminal/supports-color.d.ts +1 -0
- package/dist/terminal/supports-color.d.ts.map +1 -0
- package/dist/terminal/supports-hyperlinks.d.ts +1 -3
- package/dist/terminal/supports-hyperlinks.d.ts.map +1 -0
- package/dist/terminal/supports-hyperlinks.js +1 -1
- package/dist/terminal/types.d.ts +1 -37
- package/dist/terminal/types.d.ts.map +1 -0
- package/dist/terminal/wrap-ansi.d.ts +8 -0
- package/dist/terminal/wrap-ansi.d.ts.map +1 -0
- package/dist/terminal/wrap-ansi.js +190 -0
- package/dist/{token-utils.d.ts → tokens/counter.d.ts} +1 -0
- package/dist/tokens/counter.d.ts.map +1 -0
- package/dist/{token-utils.js → tokens/counter.js} +1 -1
- package/dist/tokens/manage-output.d.ts +34 -0
- package/dist/tokens/manage-output.d.ts.map +1 -0
- package/dist/tokens/manage-output.js +44 -0
- package/dist/{token-tracker.d.ts → tokens/tracker.d.ts} +1 -0
- package/dist/tokens/tracker.d.ts.map +1 -0
- package/dist/tool-executor.d.ts +28 -0
- package/dist/tool-executor.d.ts.map +1 -0
- package/dist/tool-executor.js +74 -0
- package/dist/tools/agent.d.ts +3 -2
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +7 -4
- package/dist/tools/bash-utils.d.ts +7 -0
- package/dist/tools/bash-utils.d.ts.map +1 -0
- package/dist/tools/bash-utils.js +212 -0
- package/dist/tools/bash.d.ts +9 -7
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +95 -212
- package/dist/tools/code-interpreter.d.ts +1 -0
- package/dist/tools/code-interpreter.d.ts.map +1 -0
- package/dist/tools/code-interpreter.js +33 -8
- package/dist/tools/delete-file.d.ts +5 -3
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +47 -33
- package/dist/tools/directory-tree.d.ts +10 -1
- package/dist/tools/directory-tree.d.ts.map +1 -0
- package/dist/tools/directory-tree.js +91 -8
- package/dist/tools/dynamic-tool-loader.d.ts +12 -0
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -0
- package/dist/tools/dynamic-tool-loader.js +280 -0
- package/dist/tools/dynamic-tool-parser.d.ts +20 -0
- package/dist/tools/dynamic-tool-parser.d.ts.map +1 -0
- package/dist/tools/dynamic-tool-parser.js +21 -0
- package/dist/tools/edit-file.d.ts +10 -2
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +117 -40
- package/dist/tools/file-editing-utils.d.ts +2 -0
- package/dist/tools/file-editing-utils.d.ts.map +1 -0
- package/dist/tools/file-editing-utils.js +135 -0
- package/dist/tools/filesystem-utils.d.ts +6 -21
- package/dist/tools/filesystem-utils.d.ts.map +1 -0
- package/dist/tools/filesystem-utils.js +96 -148
- package/dist/tools/git-utils.d.ts +1 -0
- package/dist/tools/git-utils.d.ts.map +1 -0
- package/dist/tools/grep.d.ts +5 -3
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +67 -27
- package/dist/tools/index.d.ts +10 -14
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +33 -22
- package/dist/tools/move-file.d.ts +1 -0
- package/dist/tools/move-file.d.ts.map +1 -0
- package/dist/tools/move-file.js +12 -5
- package/dist/tools/read-file.d.ts +2 -1
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +13 -6
- package/dist/tools/read-multiple-files.d.ts +2 -1
- package/dist/tools/read-multiple-files.d.ts.map +1 -0
- package/dist/tools/read-multiple-files.js +90 -9
- package/dist/tools/save-file.d.ts +5 -3
- package/dist/tools/save-file.d.ts.map +1 -0
- package/dist/tools/save-file.js +64 -36
- package/dist/tools/think.d.ts +1 -0
- package/dist/tools/think.d.ts.map +1 -0
- package/dist/tools/think.js +5 -1
- package/dist/tools/types.d.ts +14 -1
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/web-fetch.d.ts +4 -2
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +2 -2
- package/dist/tools/web-search.d.ts +2 -1
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +46 -11
- package/dist/utils/filesystem.d.ts +23 -0
- package/dist/utils/filesystem.d.ts.map +1 -0
- package/dist/utils/filesystem.js +140 -0
- package/dist/utils/filetype-detection.d.ts +3 -0
- package/dist/utils/filetype-detection.d.ts.map +1 -0
- package/dist/utils/filetype-detection.js +112 -0
- package/dist/utils/glob.d.ts +52 -0
- package/dist/utils/glob.d.ts.map +1 -0
- package/dist/utils/glob.js +376 -0
- package/dist/utils/ignore.d.ts +104 -0
- package/dist/utils/ignore.d.ts.map +1 -0
- package/dist/utils/ignore.js +649 -0
- package/dist/utils/process.d.ts +10 -1
- package/dist/utils/process.d.ts.map +1 -0
- package/dist/utils/process.js +104 -5
- package/dist/utils/zod-utils.d.ts +4 -0
- package/dist/utils/zod-utils.d.ts.map +1 -0
- package/dist/utils/zod-utils.js +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +32 -30
- package/dist/tools/command-validation.d.ts +0 -12
- package/dist/tools/command-validation.js +0 -113
- /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
|
|
5
|
-
export declare function validatePath(requestedPath: string, allowedDirectory: string
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
if (!isRealPathAllowed) {
|
|
83
|
+
if (!isWithinAllowed(normalizedReal)) {
|
|
41
84
|
throw new Error("Access denied - symlink target outside allowed directories");
|
|
42
85
|
}
|
|
43
|
-
|
|
86
|
+
validatedPath = realPath;
|
|
44
87
|
}
|
|
45
88
|
catch (_error) {
|
|
46
|
-
// For new files
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
88
|
-
//
|
|
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
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
128
|
+
await fs.stat(validatedPath);
|
|
167
129
|
}
|
|
168
|
-
catch (
|
|
169
|
-
|
|
170
|
-
|
|
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
|
}
|
|
@@ -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"}
|
package/dist/tools/grep.d.ts
CHANGED
|
@@ -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"}
|
package/dist/tools/grep.js
CHANGED
|
@@ -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:
|
|
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
|
|
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("
|
|
21
|
-
ignoreCase: z
|
|
24
|
+
.describe("Search recursively. (default: true))"),
|
|
25
|
+
ignoreCase: z.coerce
|
|
22
26
|
.boolean()
|
|
23
27
|
.nullable()
|
|
24
|
-
.describe("
|
|
28
|
+
.describe("Use case-sensitive search. (default: false)"),
|
|
25
29
|
filePattern: z
|
|
26
30
|
.string()
|
|
27
31
|
.nullable()
|
|
28
|
-
.describe("
|
|
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("
|
|
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("
|
|
37
|
-
literal: z
|
|
40
|
+
.describe("Search ignored files. (Default: false)"),
|
|
41
|
+
literal: z.coerce
|
|
38
42
|
.boolean()
|
|
39
43
|
.nullable()
|
|
40
|
-
.describe("Pass true
|
|
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
|
|
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
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
})
|
|
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 ${
|
|
140
|
+
data: `Found ${style.cyan(matchCount)} matches. (${managed.tokenCount} tokens)`,
|
|
101
141
|
});
|
|
102
|
-
return Promise.resolve(
|
|
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
|
-
|
|
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" });
|