janito 1.3.2__py3-none-any.whl → 1.4.0__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.
@@ -1,58 +1,31 @@
1
+ from janito.agent.tools.tool_base import ToolBase
2
+ from janito.agent.tool_handler import ToolHandler
3
+ from janito.agent.tools.rich_utils import print_info, print_success
1
4
  import os
2
5
  import fnmatch
3
- from janito.agent.tool_handler import ToolHandler
4
- from janito.agent.tools.rich_utils import print_info, print_success, print_error
5
6
 
6
- @ToolHandler.register_tool
7
- def find_files(
8
- directory: str,
9
- pattern: str,
10
- recursive: bool = False,
11
- max_results: int = 100
12
- ) -> str:
13
- """
14
- Find files in a directory matching a pattern.
15
- Args:
16
- directory (str): The directory to search in.
17
- pattern (str): The filename pattern to match (e.g., '*.txt').
18
- recursive (bool): Whether to search subdirectories.
19
- max_results (int): Maximum number of results to return.
20
- Returns:
21
- str: Newline-separated list of matching file paths, with summary and warnings if truncated.
22
- """
23
- print_info(f"🔍 find_files | Dir: {directory} | Pattern: {pattern} | Recursive: {recursive} | Max: {max_results}")
24
- # Input validation
25
- if not os.path.isdir(directory):
26
- print_error(f"❌ Not a directory: {directory}")
27
- return ""
28
- if not isinstance(max_results, int) or max_results <= 0:
29
- print_error(f"❌ Invalid max_results value: {max_results}")
30
- return ""
31
- matches = []
32
- try:
33
- if recursive:
34
- for root, dirs, files in os.walk(directory):
35
- for name in files:
36
- if fnmatch.fnmatch(name, pattern):
37
- matches.append(os.path.join(root, name))
38
- if len(matches) >= max_results:
39
- break
7
+ class FindFilesTool(ToolBase):
8
+ """Find files in a directory matching a pattern."""
9
+ def call(self, directory: str, pattern: str, recursive: bool=False, max_results: int=100) -> str:
10
+ import os
11
+ def _display_path(path):
12
+ import os
13
+ if os.path.isabs(path):
14
+ return path
15
+ return os.path.relpath(path)
16
+ disp_path = _display_path(directory)
17
+ rec = "recursively" if recursive else "non-recursively"
18
+ print_info(f"\U0001F50D Searching '{disp_path}' for pattern '{pattern}' ({rec}, max {max_results})")
19
+ self.update_progress(f"Searching for files in {directory} matching {pattern}")
20
+ matches = []
21
+ for root, dirs, files in os.walk(directory):
22
+ for filename in fnmatch.filter(files, pattern):
23
+ matches.append(os.path.join(root, filename))
40
24
  if len(matches) >= max_results:
41
25
  break
42
- else:
43
- for name in os.listdir(directory):
44
- full_path = os.path.join(directory, name)
45
- if os.path.isfile(full_path) and fnmatch.fnmatch(name, pattern):
46
- matches.append(full_path)
47
- if len(matches) >= max_results:
48
- break
49
- except Exception as e:
50
- print_error(f"❌ Error during file search: {e}")
51
- return ""
52
- print_success(f"✅ Found {len(matches)} file(s)")
53
- result = f"Total files found: {len(matches)}\n"
54
- result += "\n".join(matches)
55
- if len(matches) == max_results:
56
- result += "\n# WARNING: Results truncated at max_results. There may be more matching files."
57
- return result
26
+ if not recursive:
27
+ break
28
+ print_success(f"✅ {len(matches)} found")
29
+ return "\n".join(matches)
58
30
 
31
+ ToolHandler.register_tool(FindFilesTool, name="find_files")
@@ -0,0 +1,21 @@
1
+ from janito.agent.tools.tool_base import ToolBase
2
+ from janito.agent.tool_handler import ToolHandler
3
+
4
+ from janito.agent.tools.rich_utils import print_info, print_success, print_error
5
+
6
+ class GetFileOutlineTool(ToolBase):
7
+ """Get an outline of a file's structure."""
8
+ def call(self, file_path: str) -> str:
9
+ print_info(f"📄 Getting outline for: {file_path}")
10
+ self.update_progress(f"Getting outline for: {file_path}")
11
+ try:
12
+ with open(file_path, 'r', encoding='utf-8') as f:
13
+ lines = f.readlines()
14
+ outline = [line.strip() for line in lines if line.strip()]
15
+ print_success(f"\u2705 Outline generated for {file_path}")
16
+ return '\n'.join(outline)
17
+ except Exception as e:
18
+ print_error(f"\u274c Error reading file: {e}")
19
+ return f"Error reading file: {e}"
20
+
21
+ ToolHandler.register_tool(GetFileOutlineTool, name="get_file_outline")
@@ -1,58 +1,34 @@
1
- import os
1
+ from janito.agent.tools.tool_base import ToolBase
2
2
  from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path, format_number
3
+ from janito.agent.tools.rich_utils import print_info, print_success, print_error
4
4
 
5
- @ToolHandler.register_tool
6
- def get_lines(
7
- file_path: str,
8
- from_line: int = None,
9
- to_line: int = None
10
- ) -> str:
11
- """
12
- Get lines from a file, optionally with a summary of lines outside the viewed range.
13
- Always returns the total number of lines in the file at the top of the output.
14
-
15
- Parameters:
16
- - file_path (string): Path to the file.
17
- - from_line (integer, optional): First line to view (1-indexed). If omitted with to_line, returns all lines.
18
- - to_line (integer, optional): Last line to view (inclusive, 1-indexed, and cannot be more than 200 lines from from_line).
19
-
20
- If both from_line and to_line are omitted, returns all lines in the file.
21
- It is recommended to request at least 100 lines or the full file for more efficient context building.
22
- """
23
- if from_line is None and to_line is None:
24
- print_info(f"📂 get_lines | Path: {format_path(file_path)} | All lines requested")
25
- else:
26
- print_info(f"📂 get_lines | Path: {format_path(file_path)} | Lines ({from_line}-{to_line})")
27
- if not os.path.isfile(file_path):
28
- print_info(f"ℹ️ File not found: {file_path}")
29
- return ""
30
- with open(file_path, "r", encoding="utf-8") as f:
31
- lines = f.readlines()
32
- total_lines = len(lines)
33
- if from_line is None and to_line is None:
34
- numbered_content = ''.join(f"{i+1}: {line}" for i, line in enumerate(lines))
35
- print_success(f"✅ Returned all {total_lines} lines")
36
- return f"Total lines in file: {total_lines}\n" + numbered_content
37
- # Validate range
38
- if from_line is None or to_line is None:
39
- print_error(f"❌ Both from_line and to_line must be provided, or neither.")
40
- return ""
41
- if from_line < 1 or to_line < from_line or (to_line - from_line > 200):
42
- print_error(f"❌ Invalid line range: {from_line}-{to_line} for file with {total_lines} lines.")
43
- return ""
44
- if to_line > total_lines:
45
- to_line = total_lines
46
- selected = lines[from_line-1:to_line]
47
- numbered_content = ''.join(f"{i}: {line}" for i, line in zip(range(from_line, to_line+1), selected))
48
- before = lines[:from_line-1]
49
- after = lines[to_line:]
50
- before_summary = f"... {len(before)} lines before ...\n" if before else ""
51
- after_summary = f"... {len(after)} lines after ...\n" if after else ""
52
- summary = before_summary + after_summary
53
- if from_line == 1 and to_line == total_lines:
54
- print_success(f"✅ Returned all {total_lines} lines")
55
- else:
56
- print_success(f"✅ Returned lines {from_line} to {to_line} of {total_lines}")
57
- total_line_info = f"Total lines in file: {total_lines}\n"
58
- return total_line_info + summary + numbered_content
5
+ class GetLinesTool(ToolBase):
6
+ """Get specific lines from a file."""
7
+ def call(self, file_path: str, from_line: int=None, to_line: int=None) -> str:
8
+ import os
9
+ def _display_path(path):
10
+ import os
11
+ if os.path.isabs(path):
12
+ return path
13
+ return os.path.relpath(path)
14
+ disp_path = _display_path(file_path)
15
+ if from_line and to_line:
16
+ count = to_line - from_line + 1
17
+ print_info(f"📄 Reading {disp_path}:{from_line} ({count} lines)", end="")
18
+ else:
19
+ print_info(f"📄 Reading {disp_path} (all lines)", end="")
20
+ self.update_progress(f"Getting lines {from_line} to {to_line} from {file_path}")
21
+ try:
22
+ with open(file_path, 'r', encoding='utf-8') as f:
23
+ lines = f.readlines()
24
+ selected = lines[(from_line-1 if from_line else 0):(to_line if to_line else None)]
25
+ if from_line and to_line:
26
+ print_success(f" {to_line - from_line + 1} lines read")
27
+ else:
28
+ print_success(f" {len(lines)} lines read")
29
+ return ''.join(selected)
30
+ except Exception as e:
31
+ print_error(f" Error: {e}")
32
+ return f"Error reading file: {e}"
33
+
34
+ ToolHandler.register_tool(GetLinesTool, name="get_lines")
@@ -1,15 +1,17 @@
1
1
  import os
2
2
  import pathspec
3
+ from janito.agent.tools.utils import expand_path
3
4
 
4
5
  _spec = None
5
6
 
6
7
 
7
8
  def load_gitignore_patterns(gitignore_path='.gitignore'):
8
9
  global _spec
10
+ gitignore_path = expand_path(gitignore_path)
9
11
  if not os.path.exists(gitignore_path):
10
12
  _spec = pathspec.PathSpec.from_lines('gitwildmatch', [])
11
13
  return _spec
12
- with open(gitignore_path, 'r') as f:
14
+ with open(gitignore_path, 'r', encoding='utf-8') as f:
13
15
  lines = f.readlines()
14
16
  _spec = pathspec.PathSpec.from_lines('gitwildmatch', lines)
15
17
  return _spec
@@ -17,6 +19,7 @@ def load_gitignore_patterns(gitignore_path='.gitignore'):
17
19
 
18
20
  def is_ignored(path):
19
21
  global _spec
22
+ path = expand_path(path)
20
23
  if _spec is None:
21
24
  _spec = load_gitignore_patterns()
22
25
  # Normalize path to be relative and use forward slashes
@@ -1,26 +1,23 @@
1
+ from janito.agent.tools.tool_base import ToolBase
1
2
  from janito.agent.tool_handler import ToolHandler
2
- from janito.agent.tools.rich_utils import print_info
3
- import py_compile
3
+ from janito.agent.tools.rich_utils import print_info, print_success, print_error
4
4
  from typing import Optional
5
+ import py_compile
5
6
 
6
- @ToolHandler.register_tool
7
- def py_compile_file(path: str, doraise: Optional[bool] = True) -> str:
8
- """
9
- Validate a Python file by compiling it with py_compile.
10
- This tool should be used to validate Python files after changes.
11
-
12
- Args:
13
- path (str): Path to the Python file to validate.
14
- doraise (Optional[bool]): If True, raise exceptions on compilation errors. Default is True.
7
+ class PyCompileTool(ToolBase):
8
+ """Validate a Python file by compiling it with py_compile."""
9
+ def call(self, file_path: str, doraise: Optional[bool] = True) -> str:
10
+ print_info(f"[py_compile] Compiling Python file: {file_path}")
11
+ self.update_progress(f"Compiling Python file: {file_path}")
12
+ try:
13
+ py_compile.compile(file_path, doraise=doraise)
14
+ print_success(f"[py_compile] Compiled successfully: {file_path}")
15
+ return f"Compiled successfully: {file_path}"
16
+ except py_compile.PyCompileError as e:
17
+ print_error(f"[py_compile] Compile error: {e}")
18
+ return f"Compile error: {e}"
19
+ except Exception as e:
20
+ print_error(f"[py_compile] Error: {e}")
21
+ return f"Error: {e}"
15
22
 
16
- Returns:
17
- str: Success message or error details if compilation fails.
18
- """
19
- print_info(f"[py_compile_file] Validating Python file: {path}")
20
- try:
21
- py_compile.compile(path, doraise=doraise)
22
- return f"Validation successful: {path} is a valid Python file."
23
- except FileNotFoundError:
24
- return f"Validation failed: File not found: {path}"
25
- except py_compile.PyCompileError as e:
26
- return f"Validation failed: {e}"
23
+ ToolHandler.register_tool(PyCompileTool, name="py_compile_file")
@@ -3,7 +3,8 @@ from janito.agent.tools.rich_utils import print_info
3
3
  import sys
4
4
  import multiprocessing
5
5
  import io
6
- from typing import Callable, Optional
6
+ from typing import Optional
7
+ from janito.agent.tools.tool_base import ToolBase
7
8
 
8
9
 
9
10
  def _run_python_code(code: str, result_queue):
@@ -20,28 +21,34 @@ def _run_python_code(code: str, result_queue):
20
21
  })
21
22
 
22
23
 
23
- @ToolHandler.register_tool
24
- def python_exec(code: str, on_progress: Optional[Callable[[dict], None]] = None) -> str:
24
+ # Converted python_exec function into PythonExecTool subclass
25
+ class PythonExecTool(ToolBase):
25
26
  """
26
27
  Execute Python code in a separate process and capture output.
27
-
28
28
  Args:
29
29
  code (str): The Python code to execute.
30
- on_progress (Optional[Callable[[dict], None]]): Optional callback function for streaming progress updates (not used).
31
-
32
30
  Returns:
33
- str: A formatted message string containing stdout, stderr, and return code.
31
+ str: Formatted stdout, stderr, and return code.
34
32
  """
35
- print_info(f"[python_exec] Executing Python code:")
36
- print_info(code)
37
- result_queue = multiprocessing.Queue()
38
- process = multiprocessing.Process(target=_run_python_code, args=(code, result_queue))
39
- process.start()
40
- process.join()
41
- if not result_queue.empty():
42
- result = result_queue.get()
43
- else:
44
- result = {'stdout': '', 'stderr': 'No result returned from process.', 'returncode': -1}
45
- print_info(f"[python_exec] Execution completed.")
46
- print_info(f"[python_exec] Return code: {result['returncode']}")
47
- return f"stdout:\n{result['stdout']}\nstderr:\n{result['stderr']}\nreturncode: {result['returncode']}"
33
+ def call(self, code: str) -> str:
34
+ print_info(f"🐍 Executing Python code ...")
35
+ print_info(code)
36
+ self.update_progress("Starting Python code execution...")
37
+ result_queue = multiprocessing.Queue()
38
+ process = multiprocessing.Process(target=_run_python_code, args=(code, result_queue))
39
+ process.start()
40
+ process.join()
41
+ if not result_queue.empty():
42
+ result = result_queue.get()
43
+ else:
44
+ result = {'stdout': '', 'stderr': 'No result returned from process.', 'returncode': -1}
45
+ self.update_progress(f"Python code execution completed with return code: {result['returncode']}")
46
+ if result['returncode'] == 0:
47
+ from janito.agent.tools.rich_utils import print_success
48
+ print_success(f"\u2705 Python code executed successfully.")
49
+ else:
50
+ from janito.agent.tools.rich_utils import print_error
51
+ print_error(f"\u274c Python code execution failed with return code {result['returncode']}")
52
+ return f"stdout:\n{result['stdout']}\nstderr:\n{result['stderr']}\nreturncode: {result['returncode']}"
53
+
54
+ ToolHandler.register_tool(PythonExecTool, name="python_exec")
@@ -1,38 +1,24 @@
1
- import os
2
- import shutil
1
+ from janito.agent.tools.tool_base import ToolBase
3
2
  from janito.agent.tool_handler import ToolHandler
4
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
3
+ import shutil
4
+ import os
5
5
 
6
- def _is_dir_empty(path):
7
- return not any(os.scandir(path))
6
+ from janito.agent.tools.rich_utils import print_info, print_success, print_error
8
7
 
9
- @ToolHandler.register_tool
10
- def remove_directory(path: str, recursive: bool = False) -> str:
11
- """
12
- Remove a directory. If recursive is False and the directory is not empty, return an error.
8
+ class RemoveDirectoryTool(ToolBase):
9
+ """Remove a directory. If recursive=False and directory not empty, raises error."""
10
+ def call(self, directory: str, recursive: bool = False) -> str:
11
+ print_info(f"🗃️ Removing directory: {directory} (recursive={recursive})")
12
+ self.update_progress(f"Removing directory: {directory} (recursive={recursive})")
13
+ try:
14
+ if recursive:
15
+ shutil.rmtree(directory)
16
+ else:
17
+ os.rmdir(directory)
18
+ print_success(f"\u2705 Directory removed: {directory}")
19
+ return f"Directory removed: {directory}"
20
+ except Exception as e:
21
+ print_error(f"\u274c Error removing directory: {e}")
22
+ return f"Error removing directory: {e}"
13
23
 
14
- Args:
15
- path (str): Path to the directory to remove.
16
- recursive (bool): Whether to remove non-empty directories recursively. Default is False.
17
- Returns:
18
- str: Result message.
19
- """
20
- if not os.path.exists(path):
21
- print_error(f"❌ Directory '{path}' does not exist.")
22
- return f"❌ Directory '{path}' does not exist."
23
- if not os.path.isdir(path):
24
- print_error(f"❌ Path '{path}' is not a directory.")
25
- return f"❌ Path '{path}' is not a directory."
26
- if recursive:
27
- print_info(f"🗑️ Recursively removing directory: '{format_path(path)}' ... ")
28
- shutil.rmtree(path)
29
- print_success("✅ Success")
30
- return f"✅ Successfully removed directory and all contents at '{path}'."
31
- else:
32
- if not _is_dir_empty(path):
33
- print_error(f"❌ Directory '{path}' is not empty. Use recursive=True to remove non-empty directories.")
34
- return f"❌ Directory '{path}' is not empty. Use recursive=True to remove non-empty directories."
35
- print_info(f"🗑️ Removing empty directory: '{format_path(path)}' ... ")
36
- os.rmdir(path)
37
- print_success("✅ Success")
38
- return f"✅ Successfully removed empty directory at '{path}'."
24
+ ToolHandler.register_tool(RemoveDirectoryTool, name="remove_directory")
@@ -1,67 +1,67 @@
1
- import os
1
+ from janito.agent.tools.tool_base import ToolBase
2
2
  from janito.agent.tool_handler import ToolHandler
3
3
  from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
4
 
5
- @ToolHandler.register_tool
6
- def replace_text_in_file(file_path: str, search_text: str, replacement_text: str, replace_all: bool = False) -> str:
7
- """
5
+ class ReplaceTextInFileTool(ToolBase):
6
+ """Replace exact occurrences of a given text in a file.
8
7
 
9
- Replace exact occurrences of a given text in a file. The match must be exact, including whitespace and indentation, to avoid breaking file syntax or formatting.
8
+ NOTE: Indentation (leading whitespace) must be included in both search_text and replacement_text. This tool does not automatically adjust or infer indentation; matches are exact, including whitespace.
9
+ """
10
+ def call(self, file_path: str, search_text: str, replacement_text: str, replace_all: bool = False) -> str:
11
+ """
12
+ Replace exact occurrences of a given text in a file.
10
13
 
11
- Args:
12
- file_path (str): Path to the plain text file.
13
- search_text (str): Text to search for (exact match).
14
- replacement_text (str): Text to replace search_text with.
15
- replace_all (bool): Whether to replace all occurrences or just the first. Default is False.
16
- Returns:
17
- str: Result message.
18
- """
19
- search_preview = (search_text[:15] + '...') if len(search_text) > 15 else search_text
20
- replace_preview = (replacement_text[:15] + '...') if len(replacement_text) > 15 else replacement_text
21
- replace_all_msg = f" | Replace all: True" if replace_all else ""
22
- print_info(f"📝 replace_text_in_file | Path: {format_path(file_path)} | Search: '{search_preview}' | Replacement: '{replace_preview}'{replace_all_msg}")
23
- if not os.path.isfile(file_path):
24
- print_error(f"❌ File not found: {file_path}")
25
- return f" Error: File not found: {file_path}"
26
- try:
27
- with open(file_path, "r", encoding="utf-8") as f:
28
- content = f.read()
29
- except PermissionError:
30
- print_error(f"❌ Permission denied: {file_path}")
31
- return f"❌ Error: Permission denied: {file_path}"
32
- except Exception as e:
33
- print_error(f"❌ Error reading file: {e}")
34
- return f"❌ Error reading file: {e}"
14
+ Args:
15
+ file_path (str): Path to the file.
16
+ search_text (str): Text to search for. Must include indentation (leading whitespace) if present in the file.
17
+ replacement_text (str): Replacement text. Must include desired indentation (leading whitespace).
18
+ replace_all (bool): If True, replace all occurrences; otherwise, only the first occurrence.
19
+ Returns:
20
+ str: Status message.
21
+ """
22
+ import os
23
+ filename = os.path.basename(file_path)
24
+ action = "all occurrences" if replace_all else "first occurrence"
25
+ # Show only concise info (lengths, not full content)
26
+ search_preview = (search_text[:20] + '...') if len(search_text) > 20 else search_text
27
+ replace_preview = (replacement_text[:20] + '...') if len(replacement_text) > 20 else replacement_text
28
+ print_info(f"\U0001F4DD Replacing in {filename}: '{search_preview}'  '{replace_preview}' ({action})", end="")
29
+ self.update_progress(f"Replacing text in {file_path}")
30
+ try:
31
+ with open(file_path, 'r', encoding='utf-8') as f:
32
+ content = f.read()
35
33
 
36
- count = content.count(search_text)
37
- if count == 0:
38
- print_info(f"ℹ️ Search text not found in file.")
39
- return f"ℹ️ No occurrences of search text found in '{file_path}'."
40
- if replace_all:
41
- new_content = content.replace(search_text, replacement_text)
42
- replaced_count = count
43
- else:
44
- if count > 1:
45
- # Find line numbers where search_text appears
46
- lines = content.splitlines()
47
- found_lines = [i+1 for i, line in enumerate(lines) if search_text in line]
48
- preview = search_text[:40] + ('...' if len(search_text) > 40 else '')
49
- print_error(f"❌ Search text found multiple times ({count}). Please provide a more exact match or set replace_all=True.")
50
- return (
51
- f"❌ Error: Search text found {count} times in '{file_path}'. "
52
- f"Preview: '{preview}'. Found at lines: {found_lines}. "
53
- f"Please provide a more exact match."
54
- )
55
- new_content = content.replace(search_text, replacement_text, 1)
56
- replaced_count = 1 if count == 1 else 0
57
- try:
58
- with open(file_path, "w", encoding="utf-8") as f:
59
- f.write(new_content)
60
- except PermissionError:
61
- print_error(f"❌ Permission denied when writing: {file_path}")
62
- return f" Error: Permission denied when writing: {file_path}"
63
- except Exception as e:
64
- print_error(f"❌ Error writing file: {e}")
65
- return f"❌ Error writing file: {e}"
66
- print_success(f" Replaced {replaced_count} occurrence(s) of search text in '{file_path}'.")
67
- return f" Replaced {replaced_count} occurrence(s) of search text in '{file_path}'."
34
+ if replace_all:
35
+ replaced_count = content.count(search_text)
36
+ new_content = content.replace(search_text, replacement_text)
37
+ else:
38
+ occurrences = content.count(search_text)
39
+ if occurrences > 1:
40
+ print_error(f" Error: Search text is not unique ({occurrences} occurrences found). Provide more detailed context.")
41
+ return f"Error: Search text is not unique ({occurrences} occurrences found) in {file_path}. Provide more detailed context for unique replacement."
42
+ replaced_count = 1 if occurrences == 1 else 0
43
+ new_content = content.replace(search_text, replacement_text, 1)
44
+ with open(file_path, 'w', encoding='utf-8') as f:
45
+ f.write(new_content)
46
+ warning = ''
47
+ if replaced_count == 0:
48
+ warning = f" [Warning: Search text not found in file]"
49
+ print_error(warning)
50
+ print_success(f" {replaced_count} replaced{warning}")
51
+ # Indentation check for agent warning
52
+ def leading_ws(line):
53
+ import re
54
+ m = re.match(r"^\s*", line)
55
+ return m.group(0) if m else ''
56
+ search_indent = leading_ws(search_text.splitlines()[0]) if search_text.splitlines() else ''
57
+ replace_indent = leading_ws(replacement_text.splitlines()[0]) if replacement_text.splitlines() else ''
58
+ indent_warning = ''
59
+ if search_indent != replace_indent:
60
+ indent_warning = f" [Warning: Indentation mismatch between search and replacement text: '{search_indent}' vs '{replace_indent}']"
61
+ return f"Text replaced in {file_path}{warning}{indent_warning}"
62
+
63
+ except Exception as e:
64
+ print_error(f" Error: {e}")
65
+ return f"Error replacing text: {e}"
66
+
67
+ ToolHandler.register_tool(ReplaceTextInFileTool, name="replace_text_in_file")
@@ -3,14 +3,14 @@ from rich.text import Text
3
3
 
4
4
  console = Console()
5
5
 
6
- def print_info(message: str):
7
- console.print(message, style="cyan")
6
+ def print_info(message: str, end="\n"):
7
+ console.print(message, style="cyan", end=end)
8
8
 
9
- def print_success(message: str):
10
- console.print(message, style="bold green")
9
+ def print_success(message: str, end="\n"):
10
+ console.print(message, style="bold green", end=end)
11
11
 
12
- def print_error(message: str):
13
- console.print(message, style="bold red")
12
+ def print_error(message: str, end="\n"):
13
+ console.print(message, style="bold red", end=end)
14
14
 
15
15
  def print_warning(message: str):
16
16
  console.print(message, style="yellow")