cecli-dev 0.93.1__py3-none-any.whl
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.
- cecli/__init__.py +20 -0
- cecli/__main__.py +4 -0
- cecli/_version.py +34 -0
- cecli/args.py +1092 -0
- cecli/args_formatter.py +228 -0
- cecli/change_tracker.py +133 -0
- cecli/coders/__init__.py +38 -0
- cecli/coders/agent_coder.py +1872 -0
- cecli/coders/architect_coder.py +63 -0
- cecli/coders/ask_coder.py +8 -0
- cecli/coders/base_coder.py +3993 -0
- cecli/coders/chat_chunks.py +116 -0
- cecli/coders/context_coder.py +52 -0
- cecli/coders/copypaste_coder.py +269 -0
- cecli/coders/editblock_coder.py +656 -0
- cecli/coders/editblock_fenced_coder.py +9 -0
- cecli/coders/editblock_func_coder.py +140 -0
- cecli/coders/editor_diff_fenced_coder.py +8 -0
- cecli/coders/editor_editblock_coder.py +8 -0
- cecli/coders/editor_whole_coder.py +8 -0
- cecli/coders/help_coder.py +15 -0
- cecli/coders/patch_coder.py +705 -0
- cecli/coders/search_replace.py +757 -0
- cecli/coders/shell.py +37 -0
- cecli/coders/single_wholefile_func_coder.py +101 -0
- cecli/coders/udiff_coder.py +428 -0
- cecli/coders/udiff_simple.py +12 -0
- cecli/coders/wholefile_coder.py +143 -0
- cecli/coders/wholefile_func_coder.py +133 -0
- cecli/commands/__init__.py +192 -0
- cecli/commands/add.py +226 -0
- cecli/commands/agent.py +51 -0
- cecli/commands/architect.py +46 -0
- cecli/commands/ask.py +44 -0
- cecli/commands/chat_mode.py +0 -0
- cecli/commands/clear.py +37 -0
- cecli/commands/code.py +46 -0
- cecli/commands/command_prefix.py +44 -0
- cecli/commands/commit.py +52 -0
- cecli/commands/context.py +47 -0
- cecli/commands/context_blocks.py +124 -0
- cecli/commands/context_management.py +51 -0
- cecli/commands/copy.py +62 -0
- cecli/commands/copy_context.py +81 -0
- cecli/commands/core.py +287 -0
- cecli/commands/diff.py +68 -0
- cecli/commands/drop.py +217 -0
- cecli/commands/editor.py +78 -0
- cecli/commands/exit.py +55 -0
- cecli/commands/git.py +57 -0
- cecli/commands/help.py +140 -0
- cecli/commands/history_search.py +40 -0
- cecli/commands/lint.py +109 -0
- cecli/commands/list_sessions.py +56 -0
- cecli/commands/load.py +85 -0
- cecli/commands/load_session.py +48 -0
- cecli/commands/load_skill.py +68 -0
- cecli/commands/ls.py +75 -0
- cecli/commands/map.py +37 -0
- cecli/commands/map_refresh.py +35 -0
- cecli/commands/model.py +118 -0
- cecli/commands/models.py +41 -0
- cecli/commands/multiline_mode.py +38 -0
- cecli/commands/paste.py +91 -0
- cecli/commands/quit.py +32 -0
- cecli/commands/read_only.py +267 -0
- cecli/commands/read_only_stub.py +270 -0
- cecli/commands/reasoning_effort.py +70 -0
- cecli/commands/remove_skill.py +68 -0
- cecli/commands/report.py +40 -0
- cecli/commands/reset.py +88 -0
- cecli/commands/run.py +99 -0
- cecli/commands/save.py +49 -0
- cecli/commands/save_session.py +43 -0
- cecli/commands/settings.py +69 -0
- cecli/commands/test.py +58 -0
- cecli/commands/think_tokens.py +74 -0
- cecli/commands/tokens.py +207 -0
- cecli/commands/undo.py +145 -0
- cecli/commands/utils/__init__.py +0 -0
- cecli/commands/utils/base_command.py +131 -0
- cecli/commands/utils/helpers.py +142 -0
- cecli/commands/utils/registry.py +53 -0
- cecli/commands/utils/save_load_manager.py +98 -0
- cecli/commands/voice.py +78 -0
- cecli/commands/weak_model.py +123 -0
- cecli/commands/web.py +87 -0
- cecli/deprecated_args.py +185 -0
- cecli/diffs.py +129 -0
- cecli/dump.py +29 -0
- cecli/editor.py +147 -0
- cecli/exceptions.py +115 -0
- cecli/format_settings.py +26 -0
- cecli/help.py +119 -0
- cecli/help_pats.py +19 -0
- cecli/helpers/__init__.py +9 -0
- cecli/helpers/copypaste.py +123 -0
- cecli/helpers/coroutines.py +8 -0
- cecli/helpers/file_searcher.py +142 -0
- cecli/helpers/model_providers.py +552 -0
- cecli/helpers/plugin_manager.py +81 -0
- cecli/helpers/profiler.py +162 -0
- cecli/helpers/requests.py +77 -0
- cecli/helpers/similarity.py +98 -0
- cecli/helpers/skills.py +577 -0
- cecli/history.py +186 -0
- cecli/io.py +1782 -0
- cecli/linter.py +304 -0
- cecli/llm.py +101 -0
- cecli/main.py +1280 -0
- cecli/mcp/__init__.py +154 -0
- cecli/mcp/oauth.py +250 -0
- cecli/mcp/server.py +278 -0
- cecli/mdstream.py +243 -0
- cecli/models.py +1255 -0
- cecli/onboarding.py +301 -0
- cecli/prompts/__init__.py +0 -0
- cecli/prompts/agent.yml +71 -0
- cecli/prompts/architect.yml +35 -0
- cecli/prompts/ask.yml +31 -0
- cecli/prompts/base.yml +99 -0
- cecli/prompts/context.yml +60 -0
- cecli/prompts/copypaste.yml +5 -0
- cecli/prompts/editblock.yml +143 -0
- cecli/prompts/editblock_fenced.yml +106 -0
- cecli/prompts/editblock_func.yml +25 -0
- cecli/prompts/editor_diff_fenced.yml +115 -0
- cecli/prompts/editor_editblock.yml +121 -0
- cecli/prompts/editor_whole.yml +46 -0
- cecli/prompts/help.yml +37 -0
- cecli/prompts/patch.yml +110 -0
- cecli/prompts/single_wholefile_func.yml +24 -0
- cecli/prompts/udiff.yml +106 -0
- cecli/prompts/udiff_simple.yml +13 -0
- cecli/prompts/utils/__init__.py +0 -0
- cecli/prompts/utils/prompt_registry.py +167 -0
- cecli/prompts/utils/system.py +56 -0
- cecli/prompts/wholefile.yml +50 -0
- cecli/prompts/wholefile_func.yml +24 -0
- cecli/queries/tree-sitter-language-pack/README.md +7 -0
- cecli/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- cecli/queries/tree-sitter-language-pack/c-tags.scm +12 -0
- cecli/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- cecli/queries/tree-sitter-language-pack/clojure-tags.scm +12 -0
- cecli/queries/tree-sitter-language-pack/commonlisp-tags.scm +127 -0
- cecli/queries/tree-sitter-language-pack/cpp-tags.scm +18 -0
- cecli/queries/tree-sitter-language-pack/csharp-tags.scm +32 -0
- cecli/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- cecli/queries/tree-sitter-language-pack/dart-tags.scm +97 -0
- cecli/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- cecli/queries/tree-sitter-language-pack/elixir-tags.scm +59 -0
- cecli/queries/tree-sitter-language-pack/elm-tags.scm +22 -0
- cecli/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- cecli/queries/tree-sitter-language-pack/go-tags.scm +49 -0
- cecli/queries/tree-sitter-language-pack/java-tags.scm +26 -0
- cecli/queries/tree-sitter-language-pack/javascript-tags.scm +96 -0
- cecli/queries/tree-sitter-language-pack/lua-tags.scm +39 -0
- cecli/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- cecli/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- cecli/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +101 -0
- cecli/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- cecli/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- cecli/queries/tree-sitter-language-pack/python-tags.scm +24 -0
- cecli/queries/tree-sitter-language-pack/r-tags.scm +27 -0
- cecli/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- cecli/queries/tree-sitter-language-pack/ruby-tags.scm +69 -0
- cecli/queries/tree-sitter-language-pack/rust-tags.scm +63 -0
- cecli/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- cecli/queries/tree-sitter-language-pack/swift-tags.scm +54 -0
- cecli/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- cecli/queries/tree-sitter-languages/README.md +24 -0
- cecli/queries/tree-sitter-languages/c-tags.scm +12 -0
- cecli/queries/tree-sitter-languages/c_sharp-tags.scm +52 -0
- cecli/queries/tree-sitter-languages/cpp-tags.scm +18 -0
- cecli/queries/tree-sitter-languages/dart-tags.scm +92 -0
- cecli/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- cecli/queries/tree-sitter-languages/elixir-tags.scm +59 -0
- cecli/queries/tree-sitter-languages/elm-tags.scm +22 -0
- cecli/queries/tree-sitter-languages/fortran-tags.scm +18 -0
- cecli/queries/tree-sitter-languages/go-tags.scm +36 -0
- cecli/queries/tree-sitter-languages/haskell-tags.scm +5 -0
- cecli/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- cecli/queries/tree-sitter-languages/java-tags.scm +26 -0
- cecli/queries/tree-sitter-languages/javascript-tags.scm +96 -0
- cecli/queries/tree-sitter-languages/julia-tags.scm +60 -0
- cecli/queries/tree-sitter-languages/kotlin-tags.scm +30 -0
- cecli/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- cecli/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- cecli/queries/tree-sitter-languages/ocaml_interface-tags.scm +104 -0
- cecli/queries/tree-sitter-languages/php-tags.scm +32 -0
- cecli/queries/tree-sitter-languages/python-tags.scm +22 -0
- cecli/queries/tree-sitter-languages/ql-tags.scm +26 -0
- cecli/queries/tree-sitter-languages/ruby-tags.scm +69 -0
- cecli/queries/tree-sitter-languages/rust-tags.scm +63 -0
- cecli/queries/tree-sitter-languages/scala-tags.scm +64 -0
- cecli/queries/tree-sitter-languages/typescript-tags.scm +44 -0
- cecli/queries/tree-sitter-languages/zig-tags.scm +20 -0
- cecli/reasoning_tags.py +82 -0
- cecli/repo.py +626 -0
- cecli/repomap.py +1368 -0
- cecli/report.py +260 -0
- cecli/resources/__init__.py +3 -0
- cecli/resources/model-metadata.json +25751 -0
- cecli/resources/model-settings.yml +2394 -0
- cecli/resources/providers.json +67 -0
- cecli/run_cmd.py +143 -0
- cecli/scrape.py +295 -0
- cecli/sendchat.py +250 -0
- cecli/sessions.py +281 -0
- cecli/special.py +203 -0
- cecli/tools/__init__.py +72 -0
- cecli/tools/command.py +103 -0
- cecli/tools/command_interactive.py +113 -0
- cecli/tools/context_manager.py +175 -0
- cecli/tools/delete_block.py +154 -0
- cecli/tools/delete_line.py +120 -0
- cecli/tools/delete_lines.py +144 -0
- cecli/tools/extract_lines.py +281 -0
- cecli/tools/finished.py +35 -0
- cecli/tools/git_branch.py +132 -0
- cecli/tools/git_diff.py +49 -0
- cecli/tools/git_log.py +43 -0
- cecli/tools/git_remote.py +39 -0
- cecli/tools/git_show.py +37 -0
- cecli/tools/git_status.py +32 -0
- cecli/tools/grep.py +242 -0
- cecli/tools/indent_lines.py +195 -0
- cecli/tools/insert_block.py +263 -0
- cecli/tools/list_changes.py +71 -0
- cecli/tools/load_skill.py +51 -0
- cecli/tools/ls.py +77 -0
- cecli/tools/remove_skill.py +51 -0
- cecli/tools/replace_all.py +113 -0
- cecli/tools/replace_line.py +135 -0
- cecli/tools/replace_lines.py +180 -0
- cecli/tools/replace_text.py +186 -0
- cecli/tools/show_numbered_context.py +137 -0
- cecli/tools/thinking.py +52 -0
- cecli/tools/undo_change.py +82 -0
- cecli/tools/update_todo_list.py +148 -0
- cecli/tools/utils/base_tool.py +64 -0
- cecli/tools/utils/helpers.py +359 -0
- cecli/tools/utils/output.py +119 -0
- cecli/tools/utils/registry.py +145 -0
- cecli/tools/view_files_matching.py +138 -0
- cecli/tools/view_files_with_symbol.py +117 -0
- cecli/tui/__init__.py +83 -0
- cecli/tui/app.py +971 -0
- cecli/tui/io.py +566 -0
- cecli/tui/styles.tcss +117 -0
- cecli/tui/widgets/__init__.py +19 -0
- cecli/tui/widgets/completion_bar.py +331 -0
- cecli/tui/widgets/file_list.py +76 -0
- cecli/tui/widgets/footer.py +165 -0
- cecli/tui/widgets/input_area.py +320 -0
- cecli/tui/widgets/key_hints.py +16 -0
- cecli/tui/widgets/output.py +354 -0
- cecli/tui/widgets/status_bar.py +279 -0
- cecli/tui/worker.py +160 -0
- cecli/urls.py +16 -0
- cecli/utils.py +499 -0
- cecli/versioncheck.py +90 -0
- cecli/voice.py +90 -0
- cecli/waiting.py +38 -0
- cecli/watch.py +316 -0
- cecli/watch_prompts.py +12 -0
- cecli/website/Gemfile +8 -0
- cecli/website/_includes/blame.md +162 -0
- cecli/website/_includes/get-started.md +22 -0
- cecli/website/_includes/help-tip.md +5 -0
- cecli/website/_includes/help.md +24 -0
- cecli/website/_includes/install.md +5 -0
- cecli/website/_includes/keys.md +4 -0
- cecli/website/_includes/model-warnings.md +67 -0
- cecli/website/_includes/multi-line.md +22 -0
- cecli/website/_includes/python-m-aider.md +5 -0
- cecli/website/_includes/recording.css +228 -0
- cecli/website/_includes/recording.md +34 -0
- cecli/website/_includes/replit-pipx.md +9 -0
- cecli/website/_includes/works-best.md +1 -0
- cecli/website/_sass/custom/custom.scss +103 -0
- cecli/website/docs/config/adv-model-settings.md +2498 -0
- cecli/website/docs/config/agent-mode.md +320 -0
- cecli/website/docs/config/aider_conf.md +548 -0
- cecli/website/docs/config/api-keys.md +90 -0
- cecli/website/docs/config/custom-commands.md +187 -0
- cecli/website/docs/config/dotenv.md +493 -0
- cecli/website/docs/config/editor.md +127 -0
- cecli/website/docs/config/mcp.md +210 -0
- cecli/website/docs/config/model-aliases.md +173 -0
- cecli/website/docs/config/options.md +890 -0
- cecli/website/docs/config/reasoning.md +210 -0
- cecli/website/docs/config/skills.md +172 -0
- cecli/website/docs/config/tui.md +126 -0
- cecli/website/docs/config.md +44 -0
- cecli/website/docs/faq.md +379 -0
- cecli/website/docs/git.md +76 -0
- cecli/website/docs/index.md +47 -0
- cecli/website/docs/install/codespaces.md +39 -0
- cecli/website/docs/install/docker.md +48 -0
- cecli/website/docs/install/optional.md +100 -0
- cecli/website/docs/install/replit.md +8 -0
- cecli/website/docs/install.md +115 -0
- cecli/website/docs/languages.md +264 -0
- cecli/website/docs/legal/contributor-agreement.md +111 -0
- cecli/website/docs/legal/privacy.md +104 -0
- cecli/website/docs/llms/anthropic.md +77 -0
- cecli/website/docs/llms/azure.md +48 -0
- cecli/website/docs/llms/bedrock.md +132 -0
- cecli/website/docs/llms/cohere.md +34 -0
- cecli/website/docs/llms/deepseek.md +32 -0
- cecli/website/docs/llms/gemini.md +49 -0
- cecli/website/docs/llms/github.md +111 -0
- cecli/website/docs/llms/groq.md +36 -0
- cecli/website/docs/llms/lm-studio.md +39 -0
- cecli/website/docs/llms/ollama.md +75 -0
- cecli/website/docs/llms/openai-compat.md +39 -0
- cecli/website/docs/llms/openai.md +58 -0
- cecli/website/docs/llms/openrouter.md +78 -0
- cecli/website/docs/llms/other.md +117 -0
- cecli/website/docs/llms/vertex.md +50 -0
- cecli/website/docs/llms/warnings.md +10 -0
- cecli/website/docs/llms/xai.md +53 -0
- cecli/website/docs/llms.md +54 -0
- cecli/website/docs/more/analytics.md +127 -0
- cecli/website/docs/more/edit-formats.md +116 -0
- cecli/website/docs/more/infinite-output.md +192 -0
- cecli/website/docs/more-info.md +8 -0
- cecli/website/docs/recordings/auto-accept-architect.md +31 -0
- cecli/website/docs/recordings/dont-drop-original-read-files.md +35 -0
- cecli/website/docs/recordings/index.md +21 -0
- cecli/website/docs/recordings/model-accepts-settings.md +69 -0
- cecli/website/docs/recordings/tree-sitter-language-pack.md +80 -0
- cecli/website/docs/repomap.md +112 -0
- cecli/website/docs/scripting.md +100 -0
- cecli/website/docs/sessions.md +213 -0
- cecli/website/docs/troubleshooting/aider-not-found.md +24 -0
- cecli/website/docs/troubleshooting/edit-errors.md +76 -0
- cecli/website/docs/troubleshooting/imports.md +62 -0
- cecli/website/docs/troubleshooting/models-and-keys.md +54 -0
- cecli/website/docs/troubleshooting/support.md +79 -0
- cecli/website/docs/troubleshooting/token-limits.md +96 -0
- cecli/website/docs/troubleshooting/warnings.md +12 -0
- cecli/website/docs/troubleshooting.md +11 -0
- cecli/website/docs/usage/browser.md +57 -0
- cecli/website/docs/usage/caching.md +49 -0
- cecli/website/docs/usage/commands.md +133 -0
- cecli/website/docs/usage/conventions.md +119 -0
- cecli/website/docs/usage/copypaste.md +136 -0
- cecli/website/docs/usage/images-urls.md +48 -0
- cecli/website/docs/usage/lint-test.md +118 -0
- cecli/website/docs/usage/modes.md +211 -0
- cecli/website/docs/usage/not-code.md +179 -0
- cecli/website/docs/usage/notifications.md +87 -0
- cecli/website/docs/usage/tips.md +79 -0
- cecli/website/docs/usage/tutorials.md +30 -0
- cecli/website/docs/usage/voice.md +121 -0
- cecli/website/docs/usage/watch.md +294 -0
- cecli/website/docs/usage.md +102 -0
- cecli/website/share/index.md +101 -0
- cecli_dev-0.93.1.dist-info/METADATA +549 -0
- cecli_dev-0.93.1.dist-info/RECORD +366 -0
- cecli_dev-0.93.1.dist-info/WHEEL +5 -0
- cecli_dev-0.93.1.dist-info/entry_points.txt +4 -0
- cecli_dev-0.93.1.dist-info/licenses/LICENSE.txt +202 -0
- cecli_dev-0.93.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
4
|
+
from cecli.tools.utils.base_tool import BaseTool
|
|
5
|
+
from cecli.tools.utils.helpers import (
|
|
6
|
+
ToolError,
|
|
7
|
+
apply_change,
|
|
8
|
+
find_pattern_indices,
|
|
9
|
+
format_tool_result,
|
|
10
|
+
generate_unified_diff_snippet,
|
|
11
|
+
handle_tool_error,
|
|
12
|
+
is_provided,
|
|
13
|
+
select_occurrence_index,
|
|
14
|
+
validate_file_for_edit,
|
|
15
|
+
)
|
|
16
|
+
from cecli.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Tool(BaseTool):
|
|
20
|
+
NORM_NAME = "insertblock"
|
|
21
|
+
SCHEMA = {
|
|
22
|
+
"type": "function",
|
|
23
|
+
"function": {
|
|
24
|
+
"name": "InsertBlock",
|
|
25
|
+
"description": (
|
|
26
|
+
"Insert a block of content into a file. Mutually Exclusive Parameters:"
|
|
27
|
+
" after_pattern, before_pattern, position."
|
|
28
|
+
),
|
|
29
|
+
"parameters": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"file_path": {"type": "string"},
|
|
33
|
+
"content": {"type": "string"},
|
|
34
|
+
"after_pattern": {"type": "string"},
|
|
35
|
+
"before_pattern": {"type": "string"},
|
|
36
|
+
"occurrence": {"type": "integer", "default": 1},
|
|
37
|
+
"change_id": {"type": "string"},
|
|
38
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
39
|
+
"position": {"type": "string", "enum": ["top", "bottom", ""]},
|
|
40
|
+
"auto_indent": {"type": "boolean", "default": True},
|
|
41
|
+
"use_regex": {"type": "boolean", "default": False},
|
|
42
|
+
},
|
|
43
|
+
"required": ["file_path", "content"],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def execute(
|
|
50
|
+
cls,
|
|
51
|
+
coder,
|
|
52
|
+
file_path,
|
|
53
|
+
content,
|
|
54
|
+
after_pattern=None,
|
|
55
|
+
before_pattern=None,
|
|
56
|
+
occurrence=1,
|
|
57
|
+
change_id=None,
|
|
58
|
+
dry_run=False,
|
|
59
|
+
position=None,
|
|
60
|
+
auto_indent=True,
|
|
61
|
+
use_regex=False,
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
Insert a block of text after or before a specified pattern using utility functions.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
coder: The coder instance
|
|
68
|
+
file_path: Path to the file to modify
|
|
69
|
+
content: The content to insert
|
|
70
|
+
after_pattern: Pattern to insert after (mutually exclusive with before_pattern and position)
|
|
71
|
+
before_pattern: Pattern to insert before (mutually exclusive with after_pattern and position)
|
|
72
|
+
occurrence: Which occurrence of the pattern to use (1-based, or -1 for last)
|
|
73
|
+
change_id: Optional ID for tracking changes
|
|
74
|
+
dry_run: If True, only simulate the change
|
|
75
|
+
position: Special position like "top" or "bottom" (mutually exclusive with before_pattern and after_pattern)
|
|
76
|
+
auto_indent: If True, automatically adjust indentation of inserted content
|
|
77
|
+
use_regex: If True, treat patterns as regular expressions
|
|
78
|
+
"""
|
|
79
|
+
tool_name = "InsertBlock"
|
|
80
|
+
try:
|
|
81
|
+
# 1. Validate parameters
|
|
82
|
+
if sum(is_provided(x) for x in [after_pattern, before_pattern, position]) != 1:
|
|
83
|
+
raise ToolError(
|
|
84
|
+
"Must specify exactly one of: after_pattern, before_pattern, or position"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# 2. Validate file and get content
|
|
88
|
+
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
89
|
+
lines = original_content.splitlines()
|
|
90
|
+
|
|
91
|
+
# Handle empty files
|
|
92
|
+
if not lines:
|
|
93
|
+
lines = [""]
|
|
94
|
+
|
|
95
|
+
# 3. Determine insertion point
|
|
96
|
+
insertion_line_idx = 0
|
|
97
|
+
pattern_type = ""
|
|
98
|
+
pattern_desc = ""
|
|
99
|
+
occurrence_str = ""
|
|
100
|
+
|
|
101
|
+
if position:
|
|
102
|
+
# Handle special positions
|
|
103
|
+
if position == "start_of_file" or position == "top":
|
|
104
|
+
insertion_line_idx = 0
|
|
105
|
+
pattern_type = "at start of"
|
|
106
|
+
elif position == "end_of_file" or position == "bottom":
|
|
107
|
+
insertion_line_idx = len(lines)
|
|
108
|
+
pattern_type = "at end of"
|
|
109
|
+
else:
|
|
110
|
+
raise ToolError(
|
|
111
|
+
f"Invalid position: '{position}'. Valid values are 'start_of_file' or"
|
|
112
|
+
" 'end_of_file'"
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
# Handle pattern-based insertion
|
|
116
|
+
pattern = after_pattern if after_pattern else before_pattern
|
|
117
|
+
pattern_type = "after" if after_pattern else "before"
|
|
118
|
+
pattern_desc = f"Pattern '{pattern}'"
|
|
119
|
+
|
|
120
|
+
# Find pattern matches
|
|
121
|
+
pattern_line_indices = find_pattern_indices(lines, pattern, use_regex=use_regex)
|
|
122
|
+
|
|
123
|
+
# Select the target occurrence
|
|
124
|
+
target_line_idx = select_occurrence_index(
|
|
125
|
+
pattern_line_indices, occurrence, pattern_desc
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Determine insertion point
|
|
129
|
+
insertion_line_idx = target_line_idx
|
|
130
|
+
if pattern_type == "after":
|
|
131
|
+
insertion_line_idx += 1 # Insert on the line *after* the matched line
|
|
132
|
+
|
|
133
|
+
# Format occurrence info for output
|
|
134
|
+
num_occurrences = len(pattern_line_indices)
|
|
135
|
+
occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
|
|
136
|
+
|
|
137
|
+
# 4. Handle indentation if requested
|
|
138
|
+
content_lines = content.splitlines()
|
|
139
|
+
|
|
140
|
+
if auto_indent and content_lines:
|
|
141
|
+
# Determine base indentation level
|
|
142
|
+
base_indent = ""
|
|
143
|
+
if insertion_line_idx > 0 and lines:
|
|
144
|
+
# Use indentation from the line before insertion point
|
|
145
|
+
reference_line_idx = min(insertion_line_idx - 1, len(lines) - 1)
|
|
146
|
+
reference_line = lines[reference_line_idx]
|
|
147
|
+
base_indent = re.match(r"^(\s*)", reference_line).group(1)
|
|
148
|
+
|
|
149
|
+
# Apply indentation to content lines, preserving relative indentation
|
|
150
|
+
if content_lines:
|
|
151
|
+
# Find minimum indentation in content to preserve relative indentation
|
|
152
|
+
content_indents = [
|
|
153
|
+
len(re.match(r"^(\s*)", line).group(1))
|
|
154
|
+
for line in content_lines
|
|
155
|
+
if line.strip()
|
|
156
|
+
]
|
|
157
|
+
min_content_indent = min(content_indents) if content_indents else 0
|
|
158
|
+
|
|
159
|
+
# Apply base indentation while preserving relative indentation
|
|
160
|
+
indented_content_lines = []
|
|
161
|
+
for line in content_lines:
|
|
162
|
+
if not line.strip(): # Empty or whitespace-only line
|
|
163
|
+
indented_content_lines.append("")
|
|
164
|
+
else:
|
|
165
|
+
# Remove existing indentation and add new base indentation
|
|
166
|
+
stripped_line = (
|
|
167
|
+
line[min_content_indent:]
|
|
168
|
+
if min_content_indent <= len(line)
|
|
169
|
+
else line
|
|
170
|
+
)
|
|
171
|
+
indented_content_lines.append(base_indent + stripped_line)
|
|
172
|
+
|
|
173
|
+
content_lines = indented_content_lines
|
|
174
|
+
|
|
175
|
+
# 5. Prepare the insertion
|
|
176
|
+
new_lines = lines[:insertion_line_idx] + content_lines + lines[insertion_line_idx:]
|
|
177
|
+
new_content = "\n".join(new_lines)
|
|
178
|
+
|
|
179
|
+
# Restore trailing newline if original file had one
|
|
180
|
+
if original_content.endswith("\n"):
|
|
181
|
+
new_content += "\n"
|
|
182
|
+
|
|
183
|
+
if original_content == new_content:
|
|
184
|
+
coder.io.tool_warning("No changes made: insertion would not change file")
|
|
185
|
+
return "Warning: No changes made (insertion would not change file)"
|
|
186
|
+
|
|
187
|
+
# 6. Generate diff for feedback
|
|
188
|
+
diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
|
189
|
+
|
|
190
|
+
# 7. Handle dry run
|
|
191
|
+
if dry_run:
|
|
192
|
+
if position:
|
|
193
|
+
dry_run_message = f"Dry run: Would insert block {pattern_type} {file_path}."
|
|
194
|
+
else:
|
|
195
|
+
dry_run_message = (
|
|
196
|
+
f"Dry run: Would insert block {pattern_type} {occurrence_str}pattern"
|
|
197
|
+
f" '{pattern}' in {file_path} at line {insertion_line_idx + 1}."
|
|
198
|
+
)
|
|
199
|
+
return format_tool_result(
|
|
200
|
+
coder,
|
|
201
|
+
tool_name,
|
|
202
|
+
"",
|
|
203
|
+
dry_run=True,
|
|
204
|
+
dry_run_message=dry_run_message,
|
|
205
|
+
diff_snippet=diff_snippet,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# 8. Apply Change (Not dry run)
|
|
209
|
+
metadata = {
|
|
210
|
+
"insertion_line_idx": insertion_line_idx,
|
|
211
|
+
"after_pattern": after_pattern,
|
|
212
|
+
"before_pattern": before_pattern,
|
|
213
|
+
"position": position,
|
|
214
|
+
"occurrence": occurrence,
|
|
215
|
+
"content": content,
|
|
216
|
+
"auto_indent": auto_indent,
|
|
217
|
+
"use_regex": use_regex,
|
|
218
|
+
}
|
|
219
|
+
final_change_id = apply_change(
|
|
220
|
+
coder,
|
|
221
|
+
abs_path,
|
|
222
|
+
rel_path,
|
|
223
|
+
original_content,
|
|
224
|
+
new_content,
|
|
225
|
+
"insertblock",
|
|
226
|
+
metadata,
|
|
227
|
+
change_id,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
coder.files_edited_by_tools.add(rel_path)
|
|
231
|
+
|
|
232
|
+
# 9. Format and return result
|
|
233
|
+
if position:
|
|
234
|
+
success_message = f"Inserted block {pattern_type} {file_path}"
|
|
235
|
+
else:
|
|
236
|
+
success_message = (
|
|
237
|
+
f"Inserted block {pattern_type} {occurrence_str}pattern in {file_path} at line"
|
|
238
|
+
f" {insertion_line_idx + 1}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return format_tool_result(
|
|
242
|
+
coder,
|
|
243
|
+
tool_name,
|
|
244
|
+
success_message,
|
|
245
|
+
change_id=final_change_id,
|
|
246
|
+
diff_snippet=diff_snippet,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
except ToolError as e:
|
|
250
|
+
# Handle errors raised by utility functions (expected errors)
|
|
251
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
coder.io.tool_error(
|
|
255
|
+
f"Error in InsertBlock: {str(e)}\n{traceback.format_exc()}"
|
|
256
|
+
) # Add traceback
|
|
257
|
+
return f"Error: {str(e)}"
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def format_output(cls, coder, mcp_server, tool_response):
|
|
261
|
+
tool_header(coder=coder, mcp_server=mcp_server, tool_response=tool_response)
|
|
262
|
+
tool_body_unwrapped(coder=coder, tool_response=tool_response)
|
|
263
|
+
tool_footer(coder=coder, tool_response=tool_response)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from cecli.tools.utils.base_tool import BaseTool
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Tool(BaseTool):
|
|
8
|
+
NORM_NAME = "listchanges"
|
|
9
|
+
SCHEMA = {
|
|
10
|
+
"type": "function",
|
|
11
|
+
"function": {
|
|
12
|
+
"name": "ListChanges",
|
|
13
|
+
"description": "List recent changes made.",
|
|
14
|
+
"parameters": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"file_path": {"type": "string"},
|
|
18
|
+
"limit": {"type": "integer", "default": 10},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def execute(cls, coder, file_path=None, limit=10):
|
|
26
|
+
"""
|
|
27
|
+
List recent changes made to files.
|
|
28
|
+
|
|
29
|
+
Parameters:
|
|
30
|
+
- coder: The Coder instance
|
|
31
|
+
- file_path: Optional path to filter changes by file
|
|
32
|
+
- limit: Maximum number of changes to list
|
|
33
|
+
|
|
34
|
+
Returns a formatted list of changes.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
# If file_path is specified, get the absolute path
|
|
38
|
+
rel_file_path = None
|
|
39
|
+
if file_path:
|
|
40
|
+
abs_path = coder.abs_root_path(file_path)
|
|
41
|
+
rel_file_path = coder.get_rel_fname(abs_path)
|
|
42
|
+
|
|
43
|
+
# Get the list of changes
|
|
44
|
+
changes = coder.change_tracker.list_changes(rel_file_path, limit)
|
|
45
|
+
|
|
46
|
+
if not changes:
|
|
47
|
+
if file_path:
|
|
48
|
+
return f"No changes found for file '{file_path}'"
|
|
49
|
+
else:
|
|
50
|
+
return "No changes have been made yet"
|
|
51
|
+
|
|
52
|
+
# Format the changes into a readable list
|
|
53
|
+
result = "Recent changes:\n"
|
|
54
|
+
for i, change in enumerate(changes):
|
|
55
|
+
change_time = datetime.fromtimestamp(change["timestamp"]).strftime("%H:%M:%S")
|
|
56
|
+
change_type = change["type"]
|
|
57
|
+
file_path = change["file_path"]
|
|
58
|
+
change_id = change["id"]
|
|
59
|
+
|
|
60
|
+
result += (
|
|
61
|
+
f"{i + 1}. [{change_id}] {change_time} - {change_type.upper()} on {file_path}\n"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
coder.io.tool_output(result) # Also print to console for user
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
coder.io.tool_error(
|
|
69
|
+
f"Error in ListChanges: {str(e)}\n{traceback.format_exc()}"
|
|
70
|
+
) # Add traceback
|
|
71
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from cecli.tools.utils.base_tool import BaseTool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Tool(BaseTool):
|
|
5
|
+
NORM_NAME = "loadskill"
|
|
6
|
+
SCHEMA = {
|
|
7
|
+
"type": "function",
|
|
8
|
+
"function": {
|
|
9
|
+
"name": "LoadSkill",
|
|
10
|
+
"description": (
|
|
11
|
+
"Load a skill by name (agent mode only). Adds skill to include list and removes"
|
|
12
|
+
" from exclude list."
|
|
13
|
+
),
|
|
14
|
+
"parameters": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"skill_name": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Name of the skill to load",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
"required": ["skill_name"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def execute(cls, coder, skill_name):
|
|
29
|
+
"""
|
|
30
|
+
Load a skill by name (agent mode only).
|
|
31
|
+
"""
|
|
32
|
+
if not skill_name:
|
|
33
|
+
return "Error: Skill name is required."
|
|
34
|
+
|
|
35
|
+
# Check if we're in agent mode
|
|
36
|
+
if not hasattr(coder, "edit_format") or coder.edit_format != "agent":
|
|
37
|
+
return "Error: Skill loading is only available in agent mode."
|
|
38
|
+
|
|
39
|
+
# Check if skills_manager is available
|
|
40
|
+
if not hasattr(coder, "skills_manager") or coder.skills_manager is None:
|
|
41
|
+
error_msg = "Error: Skills manager is not initialized. Skills may not be configured."
|
|
42
|
+
# Check if skills directories are configured
|
|
43
|
+
if hasattr(coder, "skills_directory_paths") and not coder.skills_directory_paths:
|
|
44
|
+
error_msg += (
|
|
45
|
+
"\nNo skills directories configured. Use --skills-paths to configure skill"
|
|
46
|
+
" directories."
|
|
47
|
+
)
|
|
48
|
+
return error_msg
|
|
49
|
+
|
|
50
|
+
# Use the instance method on skills_manager
|
|
51
|
+
return coder.skills_manager.load_skill(skill_name)
|
cecli/tools/ls.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from cecli.tools.utils.base_tool import BaseTool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Tool(BaseTool):
|
|
7
|
+
NORM_NAME = "ls"
|
|
8
|
+
SCHEMA = {
|
|
9
|
+
"type": "function",
|
|
10
|
+
"function": {
|
|
11
|
+
"name": "Ls",
|
|
12
|
+
"description": "List files in a directory.",
|
|
13
|
+
"parameters": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"properties": {
|
|
16
|
+
"directory": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "The directory to list.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
"required": ["directory"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def execute(cls, coder, dir_path=None, directory=None):
|
|
28
|
+
# Handle both positional and keyword arguments for backward compatibility
|
|
29
|
+
if dir_path is None and directory is not None:
|
|
30
|
+
dir_path = directory
|
|
31
|
+
elif dir_path is None:
|
|
32
|
+
return "Error: Missing directory parameter"
|
|
33
|
+
"""
|
|
34
|
+
List files in directory and optionally add some to context.
|
|
35
|
+
|
|
36
|
+
This provides information about the structure of the codebase,
|
|
37
|
+
similar to how a developer would explore directories.
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
# Make the path relative to root if it's absolute
|
|
41
|
+
if dir_path.startswith("/"):
|
|
42
|
+
rel_dir = os.path.relpath(dir_path, coder.root)
|
|
43
|
+
else:
|
|
44
|
+
rel_dir = dir_path
|
|
45
|
+
|
|
46
|
+
# Get absolute path
|
|
47
|
+
abs_dir = coder.abs_root_path(rel_dir)
|
|
48
|
+
|
|
49
|
+
# Check if path exists
|
|
50
|
+
if not os.path.exists(abs_dir):
|
|
51
|
+
coder.io.tool_output(f"⚠️ Directory '{dir_path}' not found")
|
|
52
|
+
return "Directory not found"
|
|
53
|
+
|
|
54
|
+
# Get directory contents
|
|
55
|
+
contents = []
|
|
56
|
+
try:
|
|
57
|
+
with os.scandir(abs_dir) as entries:
|
|
58
|
+
for entry in entries:
|
|
59
|
+
if entry.is_file() and not entry.name.startswith("."):
|
|
60
|
+
rel_path = os.path.join(rel_dir, entry.name)
|
|
61
|
+
contents.append(rel_path)
|
|
62
|
+
except NotADirectoryError:
|
|
63
|
+
# If it's a file, just return the file
|
|
64
|
+
contents = [rel_dir]
|
|
65
|
+
|
|
66
|
+
if contents:
|
|
67
|
+
coder.io.tool_output(f"📋 Listed {len(contents)} file(s) in '{dir_path}'")
|
|
68
|
+
if len(contents) > 10:
|
|
69
|
+
return f"Found {len(contents)} files: {', '.join(contents[:10])}..."
|
|
70
|
+
else:
|
|
71
|
+
return f"Found {len(contents)} files: {', '.join(contents)}"
|
|
72
|
+
else:
|
|
73
|
+
coder.io.tool_output(f"📋 No files found in '{dir_path}'")
|
|
74
|
+
return "No files found in directory"
|
|
75
|
+
except Exception as e:
|
|
76
|
+
coder.io.tool_error(f"Error in ls: {str(e)}")
|
|
77
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from cecli.tools.utils.base_tool import BaseTool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Tool(BaseTool):
|
|
5
|
+
NORM_NAME = "removeskill"
|
|
6
|
+
SCHEMA = {
|
|
7
|
+
"type": "function",
|
|
8
|
+
"function": {
|
|
9
|
+
"name": "RemoveSkill",
|
|
10
|
+
"description": (
|
|
11
|
+
"Remove a skill by name (agent mode only). Removes skill from include list and adds"
|
|
12
|
+
" to exclude list."
|
|
13
|
+
),
|
|
14
|
+
"parameters": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"skill_name": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Name of the skill to remove",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
"required": ["skill_name"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def execute(cls, coder, skill_name):
|
|
29
|
+
"""
|
|
30
|
+
Remove a skill by name (agent mode only).
|
|
31
|
+
"""
|
|
32
|
+
if not skill_name:
|
|
33
|
+
return "Error: Skill name is required."
|
|
34
|
+
|
|
35
|
+
# Check if we're in agent mode
|
|
36
|
+
if not hasattr(coder, "edit_format") or coder.edit_format != "agent":
|
|
37
|
+
return "Error: Skill removal is only available in agent mode."
|
|
38
|
+
|
|
39
|
+
# Check if skills_manager is available
|
|
40
|
+
if not hasattr(coder, "skills_manager") or coder.skills_manager is None:
|
|
41
|
+
error_msg = "Error: Skills manager is not initialized. Skills may not be configured."
|
|
42
|
+
# Check if skills directories are configured
|
|
43
|
+
if hasattr(coder, "skills_directory_paths") and not coder.skills_directory_paths:
|
|
44
|
+
error_msg += (
|
|
45
|
+
"\nNo skills directories configured. Use --skills-paths to configure skill"
|
|
46
|
+
" directories."
|
|
47
|
+
)
|
|
48
|
+
return error_msg
|
|
49
|
+
|
|
50
|
+
# Use the instance method on skills_manager
|
|
51
|
+
return coder.skills_manager.remove_skill(skill_name)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from cecli.tools.utils.base_tool import BaseTool
|
|
2
|
+
from cecli.tools.utils.helpers import (
|
|
3
|
+
ToolError,
|
|
4
|
+
apply_change,
|
|
5
|
+
format_tool_result,
|
|
6
|
+
generate_unified_diff_snippet,
|
|
7
|
+
handle_tool_error,
|
|
8
|
+
validate_file_for_edit,
|
|
9
|
+
)
|
|
10
|
+
from cecli.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Tool(BaseTool):
|
|
14
|
+
NORM_NAME = "replaceall"
|
|
15
|
+
SCHEMA = {
|
|
16
|
+
"type": "function",
|
|
17
|
+
"function": {
|
|
18
|
+
"name": "ReplaceAll",
|
|
19
|
+
"description": "Replace all occurrences of text in a file.",
|
|
20
|
+
"parameters": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"file_path": {"type": "string"},
|
|
24
|
+
"find_text": {"type": "string"},
|
|
25
|
+
"replace_text": {"type": "string"},
|
|
26
|
+
"change_id": {"type": "string"},
|
|
27
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
28
|
+
},
|
|
29
|
+
"required": ["file_path", "find_text", "replace_text"],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def execute(cls, coder, file_path, find_text, replace_text, change_id=None, dry_run=False):
|
|
36
|
+
"""
|
|
37
|
+
Replace all occurrences of text in a file using utility functions.
|
|
38
|
+
"""
|
|
39
|
+
# Get absolute file path
|
|
40
|
+
abs_path = coder.abs_root_path(file_path)
|
|
41
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
42
|
+
tool_name = "ReplaceAll"
|
|
43
|
+
try:
|
|
44
|
+
# 1. Validate file and get content
|
|
45
|
+
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
46
|
+
|
|
47
|
+
# 2. Count occurrences
|
|
48
|
+
count = original_content.count(find_text)
|
|
49
|
+
if count == 0:
|
|
50
|
+
coder.io.tool_warning(f"Text '{find_text}' not found in file '{file_path}'")
|
|
51
|
+
return "Warning: Text not found in file"
|
|
52
|
+
|
|
53
|
+
# 3. Perform the replacement
|
|
54
|
+
new_content = original_content.replace(find_text, replace_text)
|
|
55
|
+
|
|
56
|
+
if original_content == new_content:
|
|
57
|
+
coder.io.tool_warning("No changes made: replacement text is identical to original")
|
|
58
|
+
return "Warning: No changes made (replacement identical to original)"
|
|
59
|
+
|
|
60
|
+
# 4. Generate diff for feedback
|
|
61
|
+
diff_examples = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
|
62
|
+
|
|
63
|
+
# 5. Handle dry run
|
|
64
|
+
if dry_run:
|
|
65
|
+
dry_run_message = (
|
|
66
|
+
f"Dry run: Would replace {count} occurrences of '{find_text}' in {file_path}."
|
|
67
|
+
)
|
|
68
|
+
return format_tool_result(
|
|
69
|
+
coder,
|
|
70
|
+
tool_name,
|
|
71
|
+
"",
|
|
72
|
+
dry_run=True,
|
|
73
|
+
dry_run_message=dry_run_message,
|
|
74
|
+
diff_snippet=diff_examples,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# 6. Apply Change (Not dry run)
|
|
78
|
+
metadata = {"find_text": find_text, "replace_text": replace_text, "occurrences": count}
|
|
79
|
+
final_change_id = apply_change(
|
|
80
|
+
coder,
|
|
81
|
+
abs_path,
|
|
82
|
+
rel_path,
|
|
83
|
+
original_content,
|
|
84
|
+
new_content,
|
|
85
|
+
"replaceall",
|
|
86
|
+
metadata,
|
|
87
|
+
change_id,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
coder.files_edited_by_tools.add(rel_path)
|
|
91
|
+
|
|
92
|
+
# 7. Format and return result
|
|
93
|
+
success_message = f"Replaced {count} occurrences in {file_path}"
|
|
94
|
+
return format_tool_result(
|
|
95
|
+
coder,
|
|
96
|
+
tool_name,
|
|
97
|
+
success_message,
|
|
98
|
+
change_id=final_change_id,
|
|
99
|
+
diff_snippet=diff_examples,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
except ToolError as e:
|
|
103
|
+
# Handle errors raised by utility functions
|
|
104
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
# Handle unexpected errors
|
|
107
|
+
return handle_tool_error(coder, tool_name, e)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def format_output(cls, coder, mcp_server, tool_response):
|
|
111
|
+
tool_header(coder=coder, mcp_server=mcp_server, tool_response=tool_response)
|
|
112
|
+
tool_body_unwrapped(coder=coder, tool_response=tool_response)
|
|
113
|
+
tool_footer(coder=coder, tool_response=tool_response)
|