@travisennis/acai 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -729
- package/bin/acai +52 -0
- package/dist/agent/index.d.ts +12 -2
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +378 -168
- package/dist/agent/sub-agent.d.ts +23 -0
- package/dist/agent/sub-agent.d.ts.map +1 -0
- package/dist/agent/sub-agent.js +109 -0
- package/dist/cli/index.d.ts +26 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/{cli.js → cli/index.js} +84 -76
- package/dist/cli/stdin.d.ts +9 -0
- package/dist/cli/stdin.d.ts.map +1 -0
- package/dist/cli/stdin.js +37 -0
- package/dist/commands/copy/index.js +2 -2
- package/dist/commands/copy/utils.d.ts.map +1 -1
- package/dist/commands/copy/utils.js +15 -13
- package/dist/commands/generate-rules/index.d.ts +1 -1
- package/dist/commands/generate-rules/index.d.ts.map +1 -1
- package/dist/commands/generate-rules/index.js +16 -100
- package/dist/commands/generate-rules/service.d.ts +21 -0
- package/dist/commands/generate-rules/service.d.ts.map +1 -0
- package/dist/commands/generate-rules/service.js +103 -0
- package/dist/commands/handoff/index.js +2 -2
- package/dist/commands/health/index.js +1 -1
- package/dist/commands/health/utils.d.ts.map +1 -1
- package/dist/commands/health/utils.js +6 -0
- package/dist/commands/history/index.d.ts +1 -1
- package/dist/commands/history/index.d.ts.map +1 -1
- package/dist/commands/history/index.js +17 -18
- package/dist/commands/history/types.d.ts +38 -0
- package/dist/commands/history/types.d.ts.map +1 -1
- package/dist/commands/history/utils.d.ts.map +1 -1
- package/dist/commands/history/utils.js +63 -58
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +3 -8
- package/dist/commands/init-project/index.d.ts.map +1 -1
- package/dist/commands/init-project/index.js +3 -3
- package/dist/commands/init-project/utils.d.ts.map +1 -1
- package/dist/commands/init-project/utils.js +10 -2
- package/dist/commands/list-tools/index.d.ts.map +1 -1
- package/dist/commands/list-tools/index.js +7 -31
- package/dist/commands/manager.d.ts +2 -2
- package/dist/commands/manager.d.ts.map +1 -1
- package/dist/commands/manager.js +57 -33
- package/dist/commands/model/index.d.ts.map +1 -1
- package/dist/commands/model/index.js +20 -151
- package/dist/commands/model/model-panel.d.ts +4 -0
- package/dist/commands/model/model-panel.d.ts.map +1 -0
- package/dist/commands/model/model-panel.js +144 -0
- package/dist/commands/paste/index.d.ts.map +1 -1
- package/dist/commands/paste/index.js +59 -62
- package/dist/commands/paste/utils.d.ts.map +1 -1
- package/dist/commands/paste/utils.js +88 -58
- package/dist/commands/pickup/index.d.ts.map +1 -1
- package/dist/commands/pickup/index.js +6 -3
- package/dist/commands/pickup/utils.js +3 -3
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +33 -50
- package/dist/commands/review/index.d.ts.map +1 -1
- package/dist/commands/review/index.js +3 -117
- package/dist/commands/review/review-panel.d.ts +3 -0
- package/dist/commands/review/review-panel.d.ts.map +1 -0
- package/dist/commands/review/review-panel.js +186 -0
- package/dist/commands/review/utils.d.ts +9 -0
- package/dist/commands/review/utils.d.ts.map +1 -1
- package/dist/commands/review/utils.js +127 -68
- package/dist/commands/session/index.d.ts +1 -1
- package/dist/commands/session/index.d.ts.map +1 -1
- package/dist/commands/session/index.js +134 -112
- package/dist/commands/session/types.d.ts +7 -0
- package/dist/commands/session/types.d.ts.map +1 -1
- package/dist/commands/share/html-renderer.d.ts +25 -0
- package/dist/commands/share/html-renderer.d.ts.map +1 -0
- package/dist/commands/share/html-renderer.js +384 -0
- package/dist/commands/share/index.d.ts +3 -0
- package/dist/commands/share/index.d.ts.map +1 -0
- package/dist/commands/share/index.js +122 -0
- package/dist/commands/shell/index.d.ts.map +1 -1
- package/dist/commands/shell/index.js +16 -1
- package/dist/commands/types.d.ts +2 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/{config.d.ts → config/index.d.ts} +20 -9
- package/dist/config/index.d.ts.map +1 -0
- package/dist/{config.js → config/index.js} +43 -42
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +75 -55
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +170 -127
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +18 -36
- package/dist/models/ai-config.d.ts +1 -0
- package/dist/models/ai-config.d.ts.map +1 -1
- package/dist/models/ai-config.js +4 -3
- package/dist/models/anthropic-provider.d.ts +2 -5
- package/dist/models/anthropic-provider.d.ts.map +1 -1
- package/dist/models/anthropic-provider.js +3 -70
- package/dist/models/deepseek-provider.d.ts +1 -0
- package/dist/models/deepseek-provider.d.ts.map +1 -1
- package/dist/models/google-provider.d.ts +2 -3
- package/dist/models/google-provider.d.ts.map +1 -1
- package/dist/models/google-provider.js +0 -26
- package/dist/models/groq-provider.d.ts +1 -0
- package/dist/models/groq-provider.d.ts.map +1 -1
- package/dist/models/manager.d.ts +13 -2
- package/dist/models/manager.d.ts.map +1 -1
- package/dist/models/manager.js +20 -8
- package/dist/models/openai-provider.d.ts +5 -5
- package/dist/models/openai-provider.d.ts.map +1 -1
- package/dist/models/openai-provider.js +27 -40
- package/dist/models/opencode-zen-provider.d.ts +8 -3
- package/dist/models/opencode-zen-provider.d.ts.map +1 -1
- package/dist/models/opencode-zen-provider.js +68 -11
- package/dist/models/openrouter-provider.d.ts +24 -30
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +92 -177
- package/dist/models/providers.d.ts +1 -1
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/xai-provider.d.ts +4 -3
- package/dist/models/xai-provider.d.ts.map +1 -1
- package/dist/models/xai-provider.js +18 -18
- package/dist/modes/manager.d.ts +23 -0
- package/dist/modes/manager.d.ts.map +1 -0
- package/dist/modes/manager.js +77 -0
- package/dist/modes/prompts.d.ts +2 -0
- package/dist/modes/prompts.d.ts.map +1 -0
- package/dist/modes/prompts.js +143 -0
- package/dist/prompts/mentions.d.ts +11 -0
- package/dist/prompts/mentions.d.ts.map +1 -0
- package/dist/{mentions.js → prompts/mentions.js} +21 -80
- package/dist/prompts/system-prompt.d.ts +26 -0
- package/dist/prompts/system-prompt.d.ts.map +1 -0
- package/dist/{prompts.js → prompts/system-prompt.js} +50 -22
- package/dist/repl/index.d.ts +174 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/{repl-new.js → repl/index.js} +399 -76
- package/dist/repl/project-status.d.ts +1 -0
- package/dist/repl/project-status.d.ts.map +1 -1
- package/dist/repl/project-status.js +4 -1
- package/dist/sessions/manager.d.ts +93 -1
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +264 -9
- package/dist/sessions/summary.d.ts +4 -0
- package/dist/sessions/summary.d.ts.map +1 -0
- package/dist/sessions/summary.js +30 -0
- package/dist/{skills.d.ts → skills/index.d.ts} +14 -2
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +294 -0
- package/dist/subagents/index.d.ts +15 -0
- package/dist/subagents/index.d.ts.map +1 -0
- package/dist/subagents/index.js +231 -0
- package/dist/terminal/control.d.ts +1 -1
- package/dist/terminal/control.d.ts.map +1 -1
- package/dist/terminal/control.js +30 -9
- package/dist/terminal/east-asian-width.d.ts.map +1 -1
- package/dist/terminal/east-asian-width.js +404 -351
- package/dist/terminal/keys.d.ts +17 -0
- package/dist/terminal/keys.d.ts.map +1 -1
- package/dist/terminal/keys.js +37 -0
- package/dist/terminal/select-prompt.d.ts.map +1 -1
- package/dist/terminal/select-prompt.js +24 -12
- package/dist/terminal/string-width.d.ts.map +1 -1
- package/dist/terminal/string-width.js +25 -27
- package/dist/terminal/style.d.ts.map +1 -1
- package/dist/terminal/style.js +4 -7
- package/dist/terminal/supports-color.d.ts.map +1 -1
- package/dist/terminal/supports-color.js +41 -27
- package/dist/terminal/table/cell.d.ts +12 -0
- package/dist/terminal/table/cell.d.ts.map +1 -1
- package/dist/terminal/table/cell.js +40 -25
- package/dist/terminal/table/layout-manager.d.ts.map +1 -1
- package/dist/terminal/table/layout-manager.js +100 -68
- package/dist/terminal/table/utils.d.ts.map +1 -1
- package/dist/terminal/table/utils.js +17 -10
- package/dist/terminal/wrap-ansi.d.ts.map +1 -1
- package/dist/terminal/wrap-ansi.js +172 -103
- package/dist/tokens/tracker.d.ts +1 -0
- package/dist/tokens/tracker.d.ts.map +1 -1
- package/dist/tokens/tracker.js +3 -0
- package/dist/tools/agent.d.ts +27 -0
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +81 -0
- package/dist/tools/bash.d.ts +4 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +343 -121
- package/dist/tools/code-search.d.ts +41 -0
- package/dist/tools/code-search.d.ts.map +1 -0
- package/dist/tools/code-search.js +195 -0
- package/dist/tools/directory-tree.d.ts +3 -3
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +8 -5
- package/dist/tools/dynamic-tool-loader.d.ts +2 -5
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-loader.js +20 -4
- package/dist/tools/edit-file.d.ts +7 -7
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +164 -66
- package/dist/tools/glob.d.ts +6 -6
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +95 -55
- package/dist/tools/grep.d.ts +15 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +300 -192
- package/dist/tools/index.d.ts +143 -5
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +39 -24
- package/dist/tools/ls.d.ts +2 -2
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +7 -5
- package/dist/tools/read-file.d.ts +3 -3
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +74 -34
- package/dist/tools/save-file.d.ts +3 -3
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +11 -11
- package/dist/tools/skill.d.ts +23 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +65 -0
- package/dist/tools/think.d.ts.map +1 -1
- package/dist/tools/think.js +2 -9
- package/dist/tools/utils.d.ts +2 -0
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +12 -0
- package/dist/tools/web-fetch.d.ts +62 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +429 -0
- package/dist/tools/web-search.d.ts +62 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +226 -0
- package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
- package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/attachment-provider.js +25 -78
- package/dist/tui/autocomplete/base-provider.d.ts +1 -0
- package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
- package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.js +3 -17
- package/dist/tui/autocomplete/command-provider.d.ts +1 -0
- package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/command-provider.js +3 -0
- package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
- package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/file-search-provider.js +36 -16
- package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
- package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
- package/dist/tui/autocomplete/skill-provider.js +49 -0
- package/dist/tui/autocomplete.d.ts +2 -2
- package/dist/tui/autocomplete.d.ts.map +1 -1
- package/dist/tui/autocomplete.js +3 -5
- package/dist/tui/components/assistant-message.d.ts.map +1 -1
- package/dist/tui/components/assistant-message.js +0 -4
- package/dist/tui/components/editor.d.ts +21 -2
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +228 -236
- package/dist/tui/components/footer.d.ts +6 -4
- package/dist/tui/components/footer.d.ts.map +1 -1
- package/dist/tui/components/footer.js +49 -25
- package/dist/tui/components/markdown.d.ts +8 -5
- package/dist/tui/components/markdown.d.ts.map +1 -1
- package/dist/tui/components/markdown.js +57 -39
- package/dist/tui/components/modal.d.ts.map +1 -1
- package/dist/tui/components/modal.js +35 -33
- package/dist/tui/components/notification.d.ts +13 -2
- package/dist/tui/components/notification.d.ts.map +1 -1
- package/dist/tui/components/notification.js +37 -2
- package/dist/tui/components/progress-bar.js +1 -1
- package/dist/tui/components/select-list.d.ts +1 -0
- package/dist/tui/components/select-list.d.ts.map +1 -1
- package/dist/tui/components/select-list.js +14 -11
- package/dist/tui/components/text.d.ts +16 -0
- package/dist/tui/components/text.d.ts.map +1 -1
- package/dist/tui/components/text.js +72 -57
- package/dist/tui/components/thinking-block.d.ts +9 -0
- package/dist/tui/components/thinking-block.d.ts.map +1 -1
- package/dist/tui/components/thinking-block.js +43 -11
- package/dist/tui/components/tool-execution.d.ts +5 -1
- package/dist/tui/components/tool-execution.d.ts.map +1 -1
- package/dist/tui/components/tool-execution.js +19 -10
- package/dist/tui/components/user-message.d.ts.map +1 -1
- package/dist/tui/components/user-message.js +0 -3
- package/dist/tui/components/welcome.js +2 -2
- package/dist/tui/editor-launcher.d.ts +13 -0
- package/dist/tui/editor-launcher.d.ts.map +1 -0
- package/dist/tui/editor-launcher.js +39 -0
- package/dist/tui/index.d.ts +3 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +1 -0
- package/dist/tui/terminal.d.ts +27 -0
- package/dist/tui/terminal.d.ts.map +1 -1
- package/dist/tui/terminal.js +144 -15
- package/dist/tui/tui.d.ts +43 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +172 -41
- package/dist/utils/bash/parse.d.ts +19 -0
- package/dist/utils/bash/parse.d.ts.map +1 -0
- package/dist/utils/bash/parse.js +223 -0
- package/dist/utils/bash/quote.d.ts +6 -0
- package/dist/utils/bash/quote.d.ts.map +1 -0
- package/dist/utils/bash/quote.js +23 -0
- package/dist/utils/bash.d.ts.map +1 -1
- package/dist/utils/bash.js +211 -126
- package/dist/utils/command-protection.d.ts +28 -0
- package/dist/utils/command-protection.d.ts.map +1 -0
- package/dist/utils/command-protection.js +324 -0
- package/dist/utils/dedent.d.ts.map +1 -0
- package/dist/utils/env-expand.d.ts +2 -0
- package/dist/utils/env-expand.d.ts.map +1 -0
- package/dist/utils/env-expand.js +8 -0
- package/dist/utils/filesystem/path-display.d.ts +11 -0
- package/dist/utils/filesystem/path-display.d.ts.map +1 -0
- package/dist/utils/filesystem/path-display.js +32 -0
- package/dist/utils/filesystem/security.d.ts +2 -2
- package/dist/utils/filesystem/security.d.ts.map +1 -1
- package/dist/utils/filesystem/security.js +32 -31
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/{formatting.js → utils/formatting.js} +1 -1
- package/dist/utils/git.d.ts +4 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +30 -0
- package/dist/utils/glob.d.ts +1 -1
- package/dist/utils/glob.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/{logger.js → utils/logger.js} +1 -1
- package/dist/utils/parsing.d.ts.map +1 -0
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +90 -37
- package/dist/utils/templates.d.ts +2 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +24 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/{version.js → utils/version.js} +1 -1
- package/package.json +34 -25
- package/dist/cli.d.ts +0 -23
- package/dist/cli.d.ts.map +0 -1
- package/dist/commands/exit/index.d.ts +0 -10
- package/dist/commands/exit/index.d.ts.map +0 -1
- package/dist/commands/exit/index.js +0 -21
- package/dist/commands/exit/types.d.ts +0 -8
- package/dist/commands/exit/types.d.ts.map +0 -1
- package/dist/commands/exit/types.js +0 -1
- package/dist/commands/exit/utils.d.ts +0 -2
- package/dist/commands/exit/utils.d.ts.map +0 -1
- package/dist/commands/exit/utils.js +0 -13
- package/dist/commands/prompt/index.d.ts +0 -5
- package/dist/commands/prompt/index.d.ts.map +0 -1
- package/dist/commands/prompt/index.js +0 -126
- package/dist/commands/prompt/types.d.ts +0 -15
- package/dist/commands/prompt/types.d.ts.map +0 -1
- package/dist/commands/prompt/types.js +0 -1
- package/dist/commands/prompt/utils.d.ts +0 -12
- package/dist/commands/prompt/utils.d.ts.map +0 -1
- package/dist/commands/prompt/utils.js +0 -107
- package/dist/commands/reset/index.d.ts +0 -3
- package/dist/commands/reset/index.d.ts.map +0 -1
- package/dist/commands/reset/index.js +0 -25
- package/dist/commands/reset/types.d.ts +0 -1
- package/dist/commands/reset/types.d.ts.map +0 -1
- package/dist/commands/reset/types.js +0 -3
- package/dist/commands/save/index.d.ts +0 -3
- package/dist/commands/save/index.d.ts.map +0 -1
- package/dist/commands/save/index.js +0 -19
- package/dist/config.d.ts.map +0 -1
- package/dist/dedent.d.ts.map +0 -1
- package/dist/formatting.d.ts.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/mentions.d.ts +0 -14
- package/dist/mentions.d.ts.map +0 -1
- package/dist/parsing.d.ts.map +0 -1
- package/dist/prompts.d.ts +0 -10
- package/dist/prompts.d.ts.map +0 -1
- package/dist/repl-new.d.ts +0 -62
- package/dist/repl-new.d.ts.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -233
- package/dist/tui/autocomplete/path-provider.d.ts +0 -21
- package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
- package/dist/tui/autocomplete/path-provider.js +0 -164
- package/dist/version.d.ts.map +0 -1
- /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
- /package/dist/{dedent.js → utils/dedent.js} +0 -0
- /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
- /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
- /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
- /package/dist/{parsing.js → utils/parsing.js} +0 -0
- /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
package/dist/tools/edit-file.js
CHANGED
|
@@ -1,46 +1,73 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, constants, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { createTwoFilesPatch } from "diff";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { config } from "../config.js";
|
|
4
|
+
import { config } from "../config/index.js";
|
|
5
5
|
import { clearProjectStatusCache } from "../repl/project-status.js";
|
|
6
6
|
import style from "../terminal/style.js";
|
|
7
|
+
import { toDisplayPath } from "../utils/filesystem/path-display.js";
|
|
7
8
|
import { joinWorkingDir, validateFileNotReadOnly, validatePath, } from "../utils/filesystem/security.js";
|
|
8
9
|
export const EditFileTool = {
|
|
9
10
|
name: "Edit",
|
|
10
11
|
};
|
|
12
|
+
const MAX_EDITS_PER_CALL = 10;
|
|
11
13
|
const inputSchema = z.object({
|
|
12
14
|
path: z.string().describe("The path of the file to edit."),
|
|
13
|
-
edits: z
|
|
15
|
+
edits: z
|
|
16
|
+
.preprocess((val) => {
|
|
17
|
+
// Handle case where model passes a JSON string instead of an array
|
|
18
|
+
if (typeof val === "string") {
|
|
19
|
+
const trimmed = val.trim();
|
|
20
|
+
// Try parsing as JSON if it looks like an array
|
|
21
|
+
if (trimmed.startsWith("[")) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(trimmed);
|
|
24
|
+
if (Array.isArray(parsed)) {
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Not valid JSON, treat as a plain string
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return val;
|
|
34
|
+
}, z.array(z.object({
|
|
14
35
|
oldText: z
|
|
15
36
|
.string()
|
|
16
|
-
.describe("Text to search for - must match exactly
|
|
17
|
-
"Special characters require JSON escaping: backticks (
|
|
18
|
-
"For multi-line content, include exact newlines and indentation.")
|
|
37
|
+
.describe("Text to search for - must match exactly. The oldText must uniquely identify the location - include enough surrounding context (e.g., 3+ lines or function/class names) to ensure only ONE match exists in the file. " +
|
|
38
|
+
"Special characters require JSON escaping: backticks (`\\``...\\``), quotes, backslashes. " +
|
|
39
|
+
"For multi-line content, include exact newlines and indentation.")
|
|
40
|
+
.min(1, "oldText must be at least 1 character"),
|
|
19
41
|
newText: z.string().describe("Text to replace with"),
|
|
20
|
-
}))
|
|
42
|
+
})))
|
|
43
|
+
.describe("The edits to make to the file."),
|
|
21
44
|
});
|
|
22
|
-
export const createEditFileTool = async (
|
|
23
|
-
const
|
|
45
|
+
export const createEditFileTool = async (options) => {
|
|
46
|
+
const { primaryDir, allowedDirs } = options.workspace;
|
|
47
|
+
const allowedDirectory = allowedDirs ?? [primaryDir];
|
|
48
|
+
// Cache config at tool creation time instead of on every execute
|
|
49
|
+
const projectConfig = await config.getConfig();
|
|
24
50
|
return {
|
|
25
51
|
toolDef: {
|
|
26
|
-
description: "
|
|
27
|
-
"with new content. Exact literal matching is used: no whitespace, indentation, escape, or newline normalization is applied when locating matches. " +
|
|
28
|
-
"Provide enough context so the match is unique; otherwise the operation errors. Returns a git-style diff showing the changes made. " +
|
|
29
|
-
"Only works within allowed directories. " +
|
|
30
|
-
"Note: Special characters in oldText must be properly escaped for JSON (e.g., backticks as \\`...\\`). " +
|
|
31
|
-
"Multi-line strings require exact character-by-character matching including whitespace.",
|
|
52
|
+
description: "Edit text in files using literal search-and-replace.",
|
|
32
53
|
inputSchema,
|
|
33
54
|
},
|
|
34
55
|
display({ path, edits }) {
|
|
35
|
-
|
|
56
|
+
const displayPath = toDisplayPath(path);
|
|
57
|
+
return `${style.cyan(displayPath)} (${edits.length} edit${edits.length === 1 ? "" : "s"})`;
|
|
36
58
|
},
|
|
37
59
|
async execute({ path, edits }, { abortSignal }) {
|
|
38
60
|
if (abortSignal?.aborted) {
|
|
39
61
|
throw new Error("File editing aborted");
|
|
40
62
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
// Check for excessive edits and return helpful message to the model
|
|
64
|
+
if (edits.length > MAX_EDITS_PER_CALL) {
|
|
65
|
+
throw new Error(`Too many edits (${edits.length}). Maximum ${MAX_EDITS_PER_CALL} edits per call. ` +
|
|
66
|
+
"Please split your changes into multiple tool calls. " +
|
|
67
|
+
"For example, if you need to make 20 edits, make 2 calls with 10 edits each.");
|
|
68
|
+
}
|
|
69
|
+
const validPath = await validatePath(joinWorkingDir(path, primaryDir), allowedDirectory, { abortSignal });
|
|
70
|
+
validateFileNotReadOnly(validPath, projectConfig, primaryDir);
|
|
44
71
|
const result = await applyFileEdits(validPath, edits, false, abortSignal);
|
|
45
72
|
clearProjectStatusCache();
|
|
46
73
|
return result;
|
|
@@ -48,55 +75,99 @@ export const createEditFileTool = async ({ workingDir, allowedDirs, }) => {
|
|
|
48
75
|
};
|
|
49
76
|
};
|
|
50
77
|
// file editing and diffing utilities
|
|
78
|
+
/** Detect the line ending used in the content */
|
|
79
|
+
function detectLineEnding(content) {
|
|
80
|
+
const crlfIdx = content.indexOf("\r\n");
|
|
81
|
+
const lfIdx = content.indexOf("\n");
|
|
82
|
+
// If no line endings at all, default to LF
|
|
83
|
+
if (crlfIdx === -1 && lfIdx === -1)
|
|
84
|
+
return "\n";
|
|
85
|
+
// If CRLF exists (and either no LF or CRLF comes first), return CRLF
|
|
86
|
+
if (crlfIdx !== -1 && (lfIdx === -1 || crlfIdx < lfIdx)) {
|
|
87
|
+
return "\r\n";
|
|
88
|
+
}
|
|
89
|
+
return "\n";
|
|
90
|
+
}
|
|
91
|
+
/** Normalize line endings to LF for internal processing */
|
|
51
92
|
function normalizeLineEndings(text) {
|
|
52
|
-
return text.replace(/\r\n/g, "\n");
|
|
93
|
+
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
53
94
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
95
|
+
/** Restore original line endings after processing */
|
|
96
|
+
function restoreLineEndings(text, ending) {
|
|
97
|
+
return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
|
|
98
|
+
}
|
|
99
|
+
/** Strip UTF-8 BOM if present - users won't include invisible BOM in oldText */
|
|
100
|
+
function stripBom(content) {
|
|
101
|
+
return content.startsWith("\uFEFF")
|
|
102
|
+
? { bom: "\uFEFF", text: content.slice(1) }
|
|
103
|
+
: { bom: "", text: content };
|
|
104
|
+
}
|
|
105
|
+
function createUnifiedDiff(normalizedOriginal, normalizedNew, filepath = "file") {
|
|
58
106
|
return createTwoFilesPatch(filepath, filepath, normalizedOriginal, normalizedNew, "original", "modified");
|
|
59
107
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
108
|
+
async function validateFileReadable(filePath) {
|
|
109
|
+
try {
|
|
110
|
+
await access(filePath, constants.R_OK);
|
|
63
111
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (edits.
|
|
112
|
+
catch {
|
|
113
|
+
throw new Error(`File not found or not readable: ${filePath}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function validateEdits(edits) {
|
|
117
|
+
if (edits.some((edit) => edit.oldText.length === 0)) {
|
|
70
118
|
throw new Error("Invalid oldText in edit. The value of oldText must be at least one character");
|
|
71
119
|
}
|
|
72
|
-
|
|
73
|
-
|
|
120
|
+
}
|
|
121
|
+
async function applyEditsSequentially(edits, content, abortSignal, filePath) {
|
|
122
|
+
let modifiedContent = content;
|
|
74
123
|
for (const edit of edits) {
|
|
75
124
|
if (abortSignal?.aborted) {
|
|
76
125
|
throw new Error("File edit operation aborted during processing");
|
|
77
126
|
}
|
|
78
|
-
const result = await
|
|
127
|
+
const result = await applyNormalizedEdit(edit, modifiedContent);
|
|
79
128
|
if (result.success) {
|
|
80
129
|
modifiedContent = result.content;
|
|
81
130
|
}
|
|
131
|
+
else if (result.errorMessage) {
|
|
132
|
+
throw new Error(result.errorMessage);
|
|
133
|
+
}
|
|
82
134
|
else {
|
|
83
|
-
throw new Error(
|
|
135
|
+
throw new Error(`Could not find the exact text in ${filePath}. The oldText must match exactly including all whitespace and newlines. ` +
|
|
136
|
+
"Tip: Check for invisible characters, extra/missing whitespace, or line ending differences.");
|
|
84
137
|
}
|
|
85
138
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
139
|
+
return modifiedContent;
|
|
140
|
+
}
|
|
141
|
+
function formatDiff(diff, _filePath) {
|
|
89
142
|
let numBackticks = 3;
|
|
90
143
|
while (diff.includes("`".repeat(numBackticks))) {
|
|
91
144
|
numBackticks++;
|
|
92
145
|
}
|
|
93
|
-
|
|
146
|
+
return `${"`".repeat(numBackticks)} diff\n${diff}\n${"`".repeat(numBackticks)}`;
|
|
147
|
+
}
|
|
148
|
+
export async function applyFileEdits(filePath, edits, dryRun = false, abortSignal) {
|
|
149
|
+
if (abortSignal?.aborted) {
|
|
150
|
+
throw new Error("File edit operation aborted");
|
|
151
|
+
}
|
|
152
|
+
await validateFileReadable(filePath);
|
|
153
|
+
const rawContent = await readFile(filePath, {
|
|
154
|
+
encoding: "utf-8",
|
|
155
|
+
signal: abortSignal,
|
|
156
|
+
});
|
|
157
|
+
const { bom: originalBom, text: bomStrippedContent } = stripBom(rawContent);
|
|
158
|
+
const originalLineEnding = detectLineEnding(bomStrippedContent);
|
|
159
|
+
const content = normalizeLineEndings(bomStrippedContent);
|
|
160
|
+
validateEdits(edits);
|
|
161
|
+
const modifiedContent = await applyEditsSequentially(edits, content, abortSignal, filePath);
|
|
162
|
+
const finalContentWithLineEndings = restoreLineEndings(modifiedContent, originalLineEnding);
|
|
163
|
+
const finalContent = originalBom + finalContentWithLineEndings;
|
|
164
|
+
const diff = createUnifiedDiff(content, finalContent, filePath);
|
|
165
|
+
const formattedDiff = formatDiff(diff, filePath);
|
|
94
166
|
if (!dryRun) {
|
|
95
167
|
if (abortSignal?.aborted) {
|
|
96
168
|
throw new Error("File edit operation aborted before writing");
|
|
97
169
|
}
|
|
98
|
-
|
|
99
|
-
await writeFile(filePath, modifiedContent, {
|
|
170
|
+
await writeFile(filePath, finalContent, {
|
|
100
171
|
encoding: "utf-8",
|
|
101
172
|
signal: abortSignal,
|
|
102
173
|
});
|
|
@@ -104,37 +175,64 @@ export async function applyFileEdits(filePath, edits, dryRun = false, abortSigna
|
|
|
104
175
|
return formattedDiff;
|
|
105
176
|
}
|
|
106
177
|
/**
|
|
107
|
-
* Applies a single edit
|
|
178
|
+
* Applies a single edit with normalized line endings
|
|
179
|
+
* Returns an error if oldText matches more than one location in the file
|
|
180
|
+
*/
|
|
181
|
+
async function applyNormalizedEdit(edit, content) {
|
|
182
|
+
// Normalize line endings to match the normalized content
|
|
183
|
+
const normalizedOldText = normalizeLineEndings(edit.oldText);
|
|
184
|
+
const normalizedNewText = normalizeLineEndings(edit.newText);
|
|
185
|
+
// First, check how many matches exist (without replacing)
|
|
186
|
+
const matchCountResult = countMatches(content, normalizedOldText);
|
|
187
|
+
const matchCount = matchCountResult.count;
|
|
188
|
+
// If more than one match, require unique oldText
|
|
189
|
+
if (matchCount > 1) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
content,
|
|
193
|
+
matchCount,
|
|
194
|
+
errorMessage: `oldText matches ${matchCount} locations in the file but should match only 1. ` +
|
|
195
|
+
"Please provide a more specific oldText that includes more surrounding context (e.g., 3+ lines, " +
|
|
196
|
+
"function/class names, or unique surrounding code) to uniquely identify the location you want to edit.",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// If no matches, return failure
|
|
200
|
+
if (matchCount === 0) {
|
|
201
|
+
return { success: false, content };
|
|
202
|
+
}
|
|
203
|
+
// Exactly one match - apply the edit
|
|
204
|
+
const originalResult = applyLiteralEdit(content, normalizedOldText, normalizedNewText);
|
|
205
|
+
return { success: true, content: originalResult.content };
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Count the number of literal matches in content without replacing
|
|
108
209
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const originalResult = applyLiteralEdit(content, oldText, newText);
|
|
113
|
-
if (originalResult.matchCount > 0) {
|
|
114
|
-
return { success: true, content: originalResult.content };
|
|
210
|
+
function countMatches(content, search) {
|
|
211
|
+
if (search === "") {
|
|
212
|
+
return { count: 0 };
|
|
115
213
|
}
|
|
116
|
-
|
|
214
|
+
// Escape special regex characters for literal matching
|
|
215
|
+
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
216
|
+
const regex = new RegExp(escapedSearch, "g");
|
|
217
|
+
// Count matches without modifying content
|
|
218
|
+
const matches = content.match(regex);
|
|
219
|
+
return { count: matches ? matches.length : 0 };
|
|
117
220
|
}
|
|
118
221
|
/**
|
|
119
222
|
* Applies a literal search and replace operation
|
|
120
223
|
*/
|
|
121
224
|
function applyLiteralEdit(content, search, replace) {
|
|
122
|
-
|
|
225
|
+
if (search === "") {
|
|
226
|
+
return { matchCount: 0, content };
|
|
227
|
+
}
|
|
228
|
+
// Escape special regex characters for literal matching
|
|
229
|
+
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
230
|
+
const regex = new RegExp(escapedSearch, "g");
|
|
231
|
+
// Use replace with callback to count matches while replacing all occurrences
|
|
123
232
|
let matchCount = 0;
|
|
124
|
-
|
|
125
|
-
while (currentIndex < modifiedContent.length) {
|
|
126
|
-
const matchIndex = modifiedContent.indexOf(search, currentIndex);
|
|
127
|
-
if (matchIndex === -1) {
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
233
|
+
const modifiedContent = content.replace(regex, () => {
|
|
130
234
|
matchCount++;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
modifiedContent.slice(0, matchIndex) +
|
|
134
|
-
replace +
|
|
135
|
-
modifiedContent.slice(matchIndex + search.length);
|
|
136
|
-
// Move current index past the replacement
|
|
137
|
-
currentIndex = matchIndex + replace.length;
|
|
138
|
-
}
|
|
235
|
+
return replace;
|
|
236
|
+
});
|
|
139
237
|
return { matchCount, content: modifiedContent };
|
|
140
238
|
}
|
package/dist/tools/glob.d.ts
CHANGED
|
@@ -4,12 +4,12 @@ export declare const GlobTool: {
|
|
|
4
4
|
name: "Glob";
|
|
5
5
|
};
|
|
6
6
|
export declare const inputSchema: z.ZodObject<{
|
|
7
|
-
patterns: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]
|
|
8
|
-
path: z.ZodString
|
|
7
|
+
patterns: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
8
|
+
path: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodString>;
|
|
9
9
|
gitignore: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
|
|
10
10
|
recursive: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
|
|
11
11
|
expandDirectories: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
|
|
12
|
-
ignoreFiles: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]
|
|
12
|
+
ignoreFiles: z.ZodPipe<z.ZodTransform<{} | null | undefined, unknown>, z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
|
|
13
13
|
cwd: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedString<unknown>>>;
|
|
14
14
|
maxResults: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
|
|
15
15
|
}, z.core.$strip>;
|
|
@@ -18,12 +18,12 @@ export declare const createGlobTool: () => {
|
|
|
18
18
|
toolDef: {
|
|
19
19
|
description: string;
|
|
20
20
|
inputSchema: z.ZodObject<{
|
|
21
|
-
patterns: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]
|
|
22
|
-
path: z.ZodString
|
|
21
|
+
patterns: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
22
|
+
path: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodString>;
|
|
23
23
|
gitignore: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
|
|
24
24
|
recursive: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
|
|
25
25
|
expandDirectories: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedBoolean<unknown>>>;
|
|
26
|
-
ignoreFiles: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]
|
|
26
|
+
ignoreFiles: z.ZodPipe<z.ZodTransform<{} | null | undefined, unknown>, z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
|
|
27
27
|
cwd: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedString<unknown>>>;
|
|
28
28
|
maxResults: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
|
|
29
29
|
}, z.core.$strip>;
|
package/dist/tools/glob.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../source/tools/glob.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../source/tools/glob.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA8CvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;iBA0EtB,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;gCAMK,eAAe;wGAmBtC,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CA6CrB,CAAC"}
|
package/dist/tools/glob.js
CHANGED
|
@@ -2,17 +2,71 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as nodePath from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import style from "../terminal/style.js";
|
|
5
|
+
import { toDisplayPath } from "../utils/filesystem/path-display.js";
|
|
5
6
|
import { glob } from "../utils/glob.js";
|
|
6
7
|
import { convertNullString } from "../utils/zod.js";
|
|
7
8
|
const DEFAULT_MAX_RESULTS = 100;
|
|
9
|
+
const MAX_STAT_FILES = 1000;
|
|
10
|
+
async function getFileWithStats(filePath, effectivePath) {
|
|
11
|
+
const fullPath = nodePath.join(effectivePath, filePath);
|
|
12
|
+
try {
|
|
13
|
+
const stats = await fs.promises.stat(fullPath);
|
|
14
|
+
return {
|
|
15
|
+
path: filePath,
|
|
16
|
+
mtime: stats.mtime.getTime(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {
|
|
21
|
+
path: filePath,
|
|
22
|
+
mtime: 0,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function sortFilesByMtime(files) {
|
|
27
|
+
return files
|
|
28
|
+
.sort((a, b) => {
|
|
29
|
+
if (b.mtime !== a.mtime) {
|
|
30
|
+
return b.mtime - a.mtime;
|
|
31
|
+
}
|
|
32
|
+
return a.path.localeCompare(b.path);
|
|
33
|
+
})
|
|
34
|
+
.map((file) => file.path);
|
|
35
|
+
}
|
|
36
|
+
function formatResult(sortedFiles) {
|
|
37
|
+
return sortedFiles.length > 0
|
|
38
|
+
? sortedFiles.join("\n")
|
|
39
|
+
: "No files found matching the specified patterns.";
|
|
40
|
+
}
|
|
8
41
|
export const GlobTool = {
|
|
9
42
|
name: "Glob",
|
|
10
43
|
};
|
|
11
44
|
export const inputSchema = z.object({
|
|
12
45
|
patterns: z
|
|
13
|
-
.
|
|
46
|
+
.preprocess((val) => {
|
|
47
|
+
if (val === null || val === undefined) {
|
|
48
|
+
return "**/*";
|
|
49
|
+
}
|
|
50
|
+
if (typeof val === "string") {
|
|
51
|
+
const trimmed = val.trim();
|
|
52
|
+
if (trimmed.startsWith("[")) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(trimmed);
|
|
55
|
+
if (Array.isArray(parsed)) {
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Not valid JSON, treat as a plain glob string
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return val;
|
|
65
|
+
}, z.union([z.string(), z.array(z.string())]))
|
|
14
66
|
.describe("Glob patterns to search for (e.g., '*.ts', '**/*.test.ts', 'src/**/*.js')"),
|
|
15
|
-
path: z
|
|
67
|
+
path: z
|
|
68
|
+
.preprocess((val) => (val === null || val === undefined ? process.cwd() : val), z.string())
|
|
69
|
+
.describe("Base directory to search in"),
|
|
16
70
|
gitignore: z
|
|
17
71
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
18
72
|
.describe("Respect ignore patterns in .gitignore files. (default: true)"),
|
|
@@ -23,9 +77,28 @@ export const inputSchema = z.object({
|
|
|
23
77
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
24
78
|
.describe("Automatically expand directories to files. (default: true)"),
|
|
25
79
|
ignoreFiles: z
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
80
|
+
.preprocess((val) => {
|
|
81
|
+
const converted = convertNullString(val);
|
|
82
|
+
if (converted === null) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (typeof converted === "string") {
|
|
86
|
+
const trimmed = converted.trim();
|
|
87
|
+
if (trimmed.startsWith("[")) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(trimmed);
|
|
90
|
+
if (Array.isArray(parsed)) {
|
|
91
|
+
return parsed;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Not valid JSON, treat as a plain string
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return converted;
|
|
100
|
+
}, z.union([z.string(), z.array(z.string())]).nullable())
|
|
101
|
+
.describe("Glob patterns to look for ignore files (e.g., '.gitignore'). Pass null to use default behavior."),
|
|
29
102
|
cwd: z
|
|
30
103
|
.preprocess((val) => convertNullString(val), z.coerce.string().nullable())
|
|
31
104
|
.describe("Current working directory override. (default: process.cwd())"),
|
|
@@ -36,7 +109,7 @@ export const inputSchema = z.object({
|
|
|
36
109
|
export const createGlobTool = () => {
|
|
37
110
|
return {
|
|
38
111
|
toolDef: {
|
|
39
|
-
description:
|
|
112
|
+
description: "Find files by name pattern (e.g., *.ts).",
|
|
40
113
|
inputSchema,
|
|
41
114
|
},
|
|
42
115
|
display({ patterns, path }) {
|
|
@@ -44,69 +117,36 @@ export const createGlobTool = () => {
|
|
|
44
117
|
const patternStr = patternArray.length === 1
|
|
45
118
|
? patternArray[0]
|
|
46
119
|
: JSON.stringify(patternArray);
|
|
47
|
-
|
|
120
|
+
const displayPath = toDisplayPath(path);
|
|
121
|
+
return `${style.cyan(patternStr)} in ${style.cyan(displayPath)}`;
|
|
48
122
|
},
|
|
49
123
|
async execute({ patterns, path, gitignore, recursive, expandDirectories, ignoreFiles, cwd, maxResults, }, { abortSignal }) {
|
|
50
124
|
if (abortSignal?.aborted) {
|
|
51
125
|
throw new Error("Glob search aborted");
|
|
52
126
|
}
|
|
127
|
+
const effectivePath = typeof path === "string" && path.trim() !== "" ? path : process.cwd();
|
|
53
128
|
const patternArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
129
|
+
const effectiveMaxResults = maxResults ?? DEFAULT_MAX_RESULTS;
|
|
54
130
|
const globOptions = {
|
|
55
131
|
cwd: cwd || process.cwd(),
|
|
132
|
+
...(gitignore !== null && { gitignore }),
|
|
133
|
+
...(recursive !== null && { recursive }),
|
|
134
|
+
...(expandDirectories !== null && { expandDirectories }),
|
|
135
|
+
...(ignoreFiles !== null && { ignoreFiles }),
|
|
56
136
|
};
|
|
57
|
-
if (gitignore !== null) {
|
|
58
|
-
globOptions["gitignore"] = gitignore;
|
|
59
|
-
}
|
|
60
|
-
if (recursive !== null) {
|
|
61
|
-
globOptions["recursive"] = recursive;
|
|
62
|
-
}
|
|
63
|
-
if (expandDirectories !== null) {
|
|
64
|
-
globOptions["expandDirectories"] = expandDirectories;
|
|
65
|
-
}
|
|
66
|
-
if (ignoreFiles !== null) {
|
|
67
|
-
globOptions["ignoreFiles"] = ignoreFiles;
|
|
68
|
-
}
|
|
69
137
|
const matchingFiles = await glob(patternArray, {
|
|
70
138
|
...globOptions,
|
|
71
|
-
cwd:
|
|
139
|
+
cwd: effectivePath,
|
|
72
140
|
});
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
mtime: stats.mtime,
|
|
80
|
-
isRecent: Date.now() - stats.mtime.getTime() < 7 * 24 * 60 * 60 * 1000,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return {
|
|
85
|
-
path: filePath,
|
|
86
|
-
mtime: new Date(0),
|
|
87
|
-
isRecent: false,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}));
|
|
91
|
-
const sortedFiles = filesWithStats
|
|
92
|
-
.sort((a, b) => {
|
|
93
|
-
if (a.isRecent && !b.isRecent)
|
|
94
|
-
return -1;
|
|
95
|
-
if (!a.isRecent && b.isRecent)
|
|
96
|
-
return 1;
|
|
97
|
-
if (a.isRecent && b.isRecent) {
|
|
98
|
-
return b.mtime.getTime() - a.mtime.getTime();
|
|
99
|
-
}
|
|
100
|
-
return a.path.localeCompare(b.path);
|
|
101
|
-
})
|
|
102
|
-
.map((file) => file.path);
|
|
103
|
-
const effectiveMaxResults = maxResults ?? DEFAULT_MAX_RESULTS;
|
|
104
|
-
const limitedFiles = effectiveMaxResults && effectiveMaxResults > 0
|
|
141
|
+
const filesToStat = matchingFiles.length > MAX_STAT_FILES
|
|
142
|
+
? matchingFiles.slice(0, MAX_STAT_FILES)
|
|
143
|
+
: matchingFiles;
|
|
144
|
+
const filesWithStats = await Promise.all(filesToStat.map((filePath) => getFileWithStats(filePath, effectivePath)));
|
|
145
|
+
const sortedFiles = sortFilesByMtime(filesWithStats);
|
|
146
|
+
const result = effectiveMaxResults > 0 && sortedFiles.length > effectiveMaxResults
|
|
105
147
|
? sortedFiles.slice(0, effectiveMaxResults)
|
|
106
148
|
: sortedFiles;
|
|
107
|
-
return
|
|
108
|
-
? limitedFiles.join("\n")
|
|
109
|
-
: "No files found matching the specified patterns.";
|
|
149
|
+
return formatResult(result);
|
|
110
150
|
},
|
|
111
151
|
};
|
|
112
152
|
};
|
package/dist/tools/grep.d.ts
CHANGED
|
@@ -41,23 +41,31 @@ interface GrepOptions {
|
|
|
41
41
|
searchIgnored?: boolean | null;
|
|
42
42
|
literal?: boolean | null;
|
|
43
43
|
maxResults?: number | null;
|
|
44
|
+
likelyUnbalanced?: boolean;
|
|
44
45
|
}
|
|
45
46
|
export declare function likelyUnbalancedRegex(pattern: string): boolean;
|
|
46
47
|
/**
|
|
47
|
-
*
|
|
48
|
+
* Build grep command args array directly
|
|
48
49
|
*
|
|
49
50
|
* @param pattern - The regex pattern to search for
|
|
50
51
|
* @param path - The path to search in
|
|
51
52
|
* @param options - Additional options for the grep command
|
|
52
|
-
* @returns The
|
|
53
|
+
* @returns The args array for the grep command
|
|
53
54
|
*/
|
|
54
|
-
export declare function
|
|
55
|
+
export declare function buildGrepArgs(pattern: string, path: string, options?: GrepOptions): string[];
|
|
55
56
|
export interface ParsedMatch {
|
|
56
57
|
file?: string;
|
|
57
58
|
line: number;
|
|
58
59
|
content: string;
|
|
59
60
|
isMatch: boolean;
|
|
60
61
|
isContext?: boolean;
|
|
62
|
+
lineNumber?: number;
|
|
63
|
+
absolutePath?: string;
|
|
64
|
+
submatches?: Array<{
|
|
65
|
+
start: number;
|
|
66
|
+
end: number;
|
|
67
|
+
text: string;
|
|
68
|
+
}>;
|
|
61
69
|
}
|
|
62
70
|
interface GrepResult {
|
|
63
71
|
rawOutput: string;
|
|
@@ -69,9 +77,9 @@ interface GrepResult {
|
|
|
69
77
|
isTruncated?: boolean;
|
|
70
78
|
}
|
|
71
79
|
/**
|
|
72
|
-
* Parse ripgrep output and extract structured match information
|
|
80
|
+
* Parse ripgrep JSON output and extract structured match information
|
|
73
81
|
*/
|
|
74
|
-
export declare function
|
|
82
|
+
export declare function parseRipgrepJsonOutput(content: string): ParsedMatch[];
|
|
75
83
|
/**
|
|
76
84
|
* Count actual matches (excluding context lines)
|
|
77
85
|
*/
|
|
@@ -81,17 +89,12 @@ export declare function countActualMatches(parsed: ParsedMatch[]): number;
|
|
|
81
89
|
*/
|
|
82
90
|
export declare function countContextLines(parsed: ParsedMatch[]): number;
|
|
83
91
|
/**
|
|
84
|
-
* Truncate matches to a maximum number of results
|
|
92
|
+
* Truncate matches to a maximum number of results, preserving context lines for kept matches
|
|
85
93
|
*/
|
|
86
94
|
export declare function truncateMatches(matches: ParsedMatch[], maxResults: number | null | undefined): {
|
|
87
95
|
truncated: ParsedMatch[];
|
|
88
96
|
isTruncated: boolean;
|
|
89
97
|
};
|
|
90
|
-
|
|
91
|
-
* Extract matches from content (backwards compatibility wrapper)
|
|
92
|
-
*/
|
|
93
|
-
export declare function extractMatches(content: string): string[];
|
|
94
|
-
export declare function grepFiles(pattern: string, path: string, options?: GrepOptions): string;
|
|
95
|
-
export declare function grepFilesStructured(pattern: string, path: string, options?: GrepOptions): GrepResult;
|
|
98
|
+
export declare function grepFilesStructured(pattern: string, path: string, options?: GrepOptions, abortSignal?: AbortSignal | null): Promise<GrepResult>;
|
|
96
99
|
export {};
|
|
97
100
|
//# sourceMappingURL=grep.d.ts.map
|
package/dist/tools/grep.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../source/tools/grep.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../source/tools/grep.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,QAAA,MAAM,WAAW;;;;;;;;;;iBAoCf,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;kFAapB,eAAe;sHAoCb,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CAkErB,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;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAqLD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA8B9D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,EAAE,CAgEV;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,WAAW,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CAqDrE;AA8BD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAEhE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAE/D;AAyBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC;IAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAqDpD;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,EACzB,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,GAC/B,OAAO,CAAC,UAAU,CAAC,CAgGrB"}
|