janito 0.11.0__py3-none-any.whl → 0.13.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.
- janito/__init__.py +1 -1
- janito/__main__.py +6 -204
- janito/callbacks.py +34 -132
- janito/cli/__init__.py +6 -0
- janito/cli/agent.py +400 -0
- janito/cli/app.py +94 -0
- janito/cli/commands.py +329 -0
- janito/cli/output.py +29 -0
- janito/cli/utils.py +22 -0
- janito/config.py +358 -121
- janito/data/instructions_template.txt +28 -0
- janito/token_report.py +154 -145
- janito/tools/__init__.py +38 -21
- janito/tools/bash/bash.py +84 -0
- janito/tools/bash/unix_persistent_bash.py +184 -0
- janito/tools/bash/win_persistent_bash.py +308 -0
- janito/tools/decorators.py +2 -13
- janito/tools/delete_file.py +27 -9
- janito/tools/fetch_webpage/__init__.py +34 -0
- janito/tools/fetch_webpage/chunking.py +76 -0
- janito/tools/fetch_webpage/core.py +155 -0
- janito/tools/fetch_webpage/extractors.py +276 -0
- janito/tools/fetch_webpage/news.py +137 -0
- janito/tools/fetch_webpage/utils.py +108 -0
- janito/tools/find_files.py +106 -44
- janito/tools/move_file.py +72 -0
- janito/tools/prompt_user.py +37 -6
- janito/tools/replace_file.py +31 -4
- janito/tools/rich_console.py +176 -0
- janito/tools/search_text.py +35 -22
- janito/tools/str_replace_editor/editor.py +7 -4
- janito/tools/str_replace_editor/handlers/__init__.py +16 -0
- janito/tools/str_replace_editor/handlers/create.py +60 -0
- janito/tools/str_replace_editor/handlers/insert.py +100 -0
- janito/tools/str_replace_editor/handlers/str_replace.py +94 -0
- janito/tools/str_replace_editor/handlers/undo.py +64 -0
- janito/tools/str_replace_editor/handlers/view.py +159 -0
- janito/tools/str_replace_editor/utils.py +0 -1
- janito/tools/usage_tracker.py +136 -0
- janito-0.13.0.dist-info/METADATA +300 -0
- janito-0.13.0.dist-info/RECORD +47 -0
- janito/chat_history.py +0 -117
- janito/data/instructions.txt +0 -4
- janito/tools/bash.py +0 -22
- janito/tools/str_replace_editor/handlers.py +0 -335
- janito-0.11.0.dist-info/METADATA +0 -86
- janito-0.11.0.dist-info/RECORD +0 -26
- {janito-0.11.0.dist-info → janito-0.13.0.dist-info}/WHEEL +0 -0
- {janito-0.11.0.dist-info → janito-0.13.0.dist-info}/entry_points.txt +0 -0
- {janito-0.11.0.dist-info → janito-0.13.0.dist-info}/licenses/LICENSE +0 -0
janito/tools/find_files.py
CHANGED
@@ -1,83 +1,102 @@
|
|
1
1
|
import os
|
2
|
-
import
|
3
|
-
|
4
|
-
from
|
2
|
+
import glob
|
3
|
+
import fnmatch # Still needed for gitignore pattern matching
|
4
|
+
from typing import List, Tuple
|
5
|
+
from janito.tools.rich_console import print_info, print_success, print_error, print_warning
|
5
6
|
|
6
7
|
|
7
|
-
|
8
|
-
def find_files(pattern: str, root_dir: str = ".", recursive: bool = True, respect_gitignore: bool = True) -> Tuple[str, bool]:
|
8
|
+
def find_files(pattern: str, root_dir: str = ".", recursive: bool = True) -> Tuple[str, bool]:
|
9
9
|
"""
|
10
10
|
Find files whose path matches a glob pattern.
|
11
|
+
Files in .gitignore are always ignored.
|
11
12
|
|
12
13
|
Args:
|
13
14
|
pattern: pattern to match file paths against (e.g., "*.py", "*/tools/*.py")
|
14
15
|
root_dir: root directory to start search from (default: current directory)
|
15
16
|
recursive: Whether to search recursively in subdirectories (default: True)
|
16
|
-
respect_gitignore: Whether to respect .gitignore files (default: True)
|
17
17
|
|
18
18
|
Returns:
|
19
19
|
A tuple containing (message, is_error)
|
20
20
|
"""
|
21
|
+
# Print start message without newline
|
22
|
+
print_info(
|
23
|
+
f"Finding files matching path pattern {pattern}, on {root_dir} " +
|
24
|
+
f"({'recursive' if recursive else 'non-recursive'})",
|
25
|
+
title="Text Search"
|
26
|
+
)
|
21
27
|
try:
|
22
28
|
# Convert to absolute path if relative
|
23
29
|
abs_root = os.path.abspath(root_dir)
|
24
30
|
|
25
31
|
if not os.path.isdir(abs_root):
|
26
|
-
|
32
|
+
error_msg = f"Error: Directory '{root_dir}' does not exist"
|
33
|
+
print_error(error_msg, title="File Operation")
|
34
|
+
return error_msg, True
|
27
35
|
|
28
36
|
matching_files = []
|
29
37
|
|
30
|
-
# Get gitignore patterns
|
31
|
-
ignored_patterns =
|
32
|
-
if respect_gitignore:
|
33
|
-
ignored_patterns = _get_gitignore_patterns(abs_root)
|
38
|
+
# Get gitignore patterns
|
39
|
+
ignored_patterns = _get_gitignore_patterns(abs_root)
|
34
40
|
|
35
|
-
#
|
41
|
+
# Check if the search pattern itself is in the gitignore
|
42
|
+
if _is_pattern_ignored(pattern, ignored_patterns):
|
43
|
+
warning_msg = f"Warning: The search pattern '{pattern}' matches patterns in .gitignore. Search may not yield expected results."
|
44
|
+
print_error(warning_msg, title="Text Search")
|
45
|
+
return warning_msg, True
|
46
|
+
|
47
|
+
# Use glob for pattern matching
|
48
|
+
# Construct the glob pattern with the root directory
|
49
|
+
glob_pattern = os.path.join(abs_root, pattern) if not pattern.startswith(os.path.sep) else pattern
|
50
|
+
|
51
|
+
# Use recursive glob if needed
|
36
52
|
if recursive:
|
37
|
-
for
|
38
|
-
|
39
|
-
if
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
# Convert to relative path from root_dir
|
50
|
-
rel_path = os.path.relpath(file_path, abs_root)
|
51
|
-
# Match against the relative path, not just the filename
|
52
|
-
if fnmatch.fnmatch(rel_path, pattern):
|
53
|
-
matching_files.append(rel_path)
|
53
|
+
# Use ** pattern for recursive search if not already in the pattern
|
54
|
+
if '**' not in glob_pattern:
|
55
|
+
# Check if the pattern already has a directory component
|
56
|
+
if os.path.sep in pattern or '/' in pattern:
|
57
|
+
# Pattern already has directory component, keep as is
|
58
|
+
pass
|
59
|
+
else:
|
60
|
+
# Add ** to search in all subdirectories
|
61
|
+
glob_pattern = os.path.join(abs_root, '**', pattern)
|
62
|
+
|
63
|
+
# Use recursive=True for Python 3.5+ glob
|
64
|
+
glob_files = glob.glob(glob_pattern, recursive=True)
|
54
65
|
else:
|
55
66
|
# Non-recursive mode - only search in the specified directory
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
glob_files = glob.glob(glob_pattern)
|
68
|
+
|
69
|
+
# Process the glob results
|
70
|
+
for file_path in glob_files:
|
71
|
+
# Skip directories
|
72
|
+
if not os.path.isfile(file_path):
|
73
|
+
continue
|
62
74
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
75
|
+
# Skip ignored files
|
76
|
+
if _is_ignored(file_path, ignored_patterns, abs_root):
|
77
|
+
continue
|
78
|
+
|
79
|
+
# Convert to relative path from root_dir
|
80
|
+
rel_path = os.path.relpath(file_path, abs_root)
|
81
|
+
matching_files.append(rel_path)
|
69
82
|
|
70
83
|
# Sort the files for consistent output
|
71
84
|
matching_files.sort()
|
72
85
|
|
73
86
|
if matching_files:
|
74
87
|
file_list = "\n- ".join(matching_files)
|
75
|
-
|
88
|
+
result_msg = f"{len(matching_files)} files found"
|
89
|
+
print_success(result_msg, title="Search Results")
|
90
|
+
return file_list, False
|
76
91
|
else:
|
77
|
-
|
92
|
+
result_msg = "No files found"
|
93
|
+
print_success(result_msg, title="Search Results")
|
94
|
+
return result_msg, False
|
78
95
|
|
79
96
|
except Exception as e:
|
80
|
-
|
97
|
+
error_msg = f"Error finding files: {str(e)}"
|
98
|
+
print_error(error_msg, title="Text Search")
|
99
|
+
return error_msg, True
|
81
100
|
|
82
101
|
|
83
102
|
def _get_gitignore_patterns(root_dir: str) -> List[str]:
|
@@ -115,6 +134,49 @@ def _get_gitignore_patterns(root_dir: str) -> List[str]:
|
|
115
134
|
return patterns
|
116
135
|
|
117
136
|
|
137
|
+
def _is_pattern_ignored(search_pattern: str, gitignore_patterns: List[str]) -> bool:
|
138
|
+
"""
|
139
|
+
Check if a search pattern conflicts with gitignore patterns.
|
140
|
+
|
141
|
+
Args:
|
142
|
+
search_pattern: The search pattern to check
|
143
|
+
gitignore_patterns: List of gitignore patterns
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
True if the search pattern conflicts with gitignore patterns, False otherwise
|
147
|
+
"""
|
148
|
+
# Remove any directory part from the search pattern
|
149
|
+
pattern_only = search_pattern.split('/')[-1]
|
150
|
+
|
151
|
+
for git_pattern in gitignore_patterns:
|
152
|
+
# Skip negation patterns
|
153
|
+
if git_pattern.startswith('!'):
|
154
|
+
continue
|
155
|
+
|
156
|
+
# Remove trailing slash for directory patterns
|
157
|
+
if git_pattern.endswith('/'):
|
158
|
+
git_pattern = git_pattern[:-1]
|
159
|
+
|
160
|
+
# Direct match
|
161
|
+
if git_pattern == search_pattern or git_pattern == pattern_only:
|
162
|
+
return True
|
163
|
+
|
164
|
+
# Check if the gitignore pattern is a prefix of the search pattern
|
165
|
+
if search_pattern.startswith(git_pattern) and (
|
166
|
+
len(git_pattern) == len(search_pattern) or
|
167
|
+
search_pattern[len(git_pattern)] in ['/', '\\']
|
168
|
+
):
|
169
|
+
return True
|
170
|
+
|
171
|
+
# Check for wildcard matches
|
172
|
+
if '*' in git_pattern or '?' in git_pattern:
|
173
|
+
# Check if the search pattern would be caught by this gitignore pattern
|
174
|
+
if fnmatch.fnmatch(search_pattern, git_pattern) or fnmatch.fnmatch(pattern_only, git_pattern):
|
175
|
+
return True
|
176
|
+
|
177
|
+
return False
|
178
|
+
|
179
|
+
|
118
180
|
def _is_ignored(path: str, patterns: List[str], root_dir: str) -> bool:
|
119
181
|
"""
|
120
182
|
Check if a path should be ignored based on gitignore patterns.
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""
|
2
|
+
Tool for moving files through the claudine agent.
|
3
|
+
"""
|
4
|
+
import shutil
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Tuple
|
7
|
+
from janito.tools.str_replace_editor.utils import normalize_path
|
8
|
+
from janito.tools.rich_console import print_info, print_success, print_error
|
9
|
+
from janito.tools.usage_tracker import track_usage
|
10
|
+
|
11
|
+
|
12
|
+
@track_usage('files_moved')
|
13
|
+
def move_file(
|
14
|
+
source_path: str,
|
15
|
+
destination_path: str,
|
16
|
+
) -> Tuple[str, bool]:
|
17
|
+
"""
|
18
|
+
Move a file from source path to destination path.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
source_path: Path to the file to move, relative to the workspace directory
|
22
|
+
destination_path: Destination path where the file should be moved, relative to the workspace directory
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A tuple containing (message, is_error)
|
26
|
+
"""
|
27
|
+
print_info(f"Moving file from {source_path} to {destination_path}", "Move Operation")
|
28
|
+
|
29
|
+
# Store the original paths for display purposes
|
30
|
+
original_source = source_path
|
31
|
+
original_destination = destination_path
|
32
|
+
|
33
|
+
# Normalize the file paths (converts to absolute paths)
|
34
|
+
source = normalize_path(source_path)
|
35
|
+
destination = normalize_path(destination_path)
|
36
|
+
|
37
|
+
# Convert to Path objects for better path handling
|
38
|
+
source_obj = Path(source)
|
39
|
+
destination_obj = Path(destination)
|
40
|
+
|
41
|
+
# Check if the source file exists
|
42
|
+
if not source_obj.exists():
|
43
|
+
error_msg = f"Source file {original_source} does not exist."
|
44
|
+
print_error(error_msg, "Error")
|
45
|
+
return (error_msg, True)
|
46
|
+
|
47
|
+
# Check if source is a directory
|
48
|
+
if source_obj.is_dir():
|
49
|
+
error_msg = f"{original_source} is a directory, not a file. Use move_directory for directories."
|
50
|
+
print_error(error_msg, "Error")
|
51
|
+
return (error_msg, True)
|
52
|
+
|
53
|
+
# Check if destination directory exists
|
54
|
+
if not destination_obj.parent.exists():
|
55
|
+
try:
|
56
|
+
destination_obj.parent.mkdir(parents=True, exist_ok=True)
|
57
|
+
print_info(f"Created directory: {destination_obj.parent}", "Info")
|
58
|
+
except Exception as e:
|
59
|
+
error_msg = f"Error creating destination directory: {str(e)}"
|
60
|
+
print_error(error_msg, "Error")
|
61
|
+
return (error_msg, True)
|
62
|
+
|
63
|
+
# Move the file
|
64
|
+
try:
|
65
|
+
shutil.move(str(source_obj), str(destination_obj))
|
66
|
+
success_msg = f"Successfully moved file from {original_source} to {original_destination}"
|
67
|
+
print_success(success_msg, "Success")
|
68
|
+
return (success_msg, False)
|
69
|
+
except Exception as e:
|
70
|
+
error_msg = f"Error moving file from {original_source} to {original_destination}: {str(e)}"
|
71
|
+
print_error(error_msg, "Error")
|
72
|
+
return (error_msg, True)
|
janito/tools/prompt_user.py
CHANGED
@@ -1,16 +1,24 @@
|
|
1
1
|
"""
|
2
2
|
Tool for prompting the user for input through the claudine agent.
|
3
3
|
"""
|
4
|
-
from typing import Tuple
|
5
|
-
|
4
|
+
from typing import Tuple, List
|
5
|
+
import sys
|
6
|
+
import textwrap
|
7
|
+
from rich.console import Console
|
8
|
+
from janito.tools.rich_console import print_info, print_error, print_warning
|
9
|
+
from janito.tools.usage_tracker import track_usage
|
10
|
+
from janito.cli.utils import get_stdin_termination_hint
|
6
11
|
|
7
12
|
|
8
|
-
|
13
|
+
console = Console()
|
14
|
+
|
15
|
+
@track_usage('user_prompts')
|
9
16
|
def prompt_user(
|
10
17
|
prompt_text: str,
|
11
18
|
) -> Tuple[str, bool]:
|
12
19
|
"""
|
13
20
|
Prompt the user for input and return their response.
|
21
|
+
Displays the prompt in a panel and uses stdin for input.
|
14
22
|
|
15
23
|
Args:
|
16
24
|
prompt_text: Text to display to the user as a prompt
|
@@ -19,8 +27,31 @@ def prompt_user(
|
|
19
27
|
A tuple containing (user_response, is_error)
|
20
28
|
"""
|
21
29
|
try:
|
22
|
-
#
|
23
|
-
|
30
|
+
# Display the prompt with ASCII header
|
31
|
+
console.print("\n" + "="*50)
|
32
|
+
console.print("USER PROMPT")
|
33
|
+
console.print("="*50)
|
34
|
+
console.print(prompt_text)
|
35
|
+
|
36
|
+
# Show input instructions with stdin termination hint
|
37
|
+
termination_hint = get_stdin_termination_hint().replace("[bold yellow]", "").replace("[/bold yellow]", "")
|
38
|
+
print_info(f"Enter your response below. {termination_hint}\n", "Input Instructions")
|
39
|
+
|
40
|
+
# Read input from stdin
|
41
|
+
lines = []
|
42
|
+
for line in sys.stdin:
|
43
|
+
lines.append(line.rstrip('\n'))
|
44
|
+
|
45
|
+
# Join the lines with newlines to preserve the multiline format
|
46
|
+
user_response = "\n".join(lines)
|
47
|
+
|
48
|
+
# If no input was provided, return a message
|
49
|
+
if not user_response.strip():
|
50
|
+
print_warning("No input was provided. Empty Input.")
|
51
|
+
return ("", False)
|
52
|
+
|
24
53
|
return (user_response, False)
|
25
54
|
except Exception as e:
|
26
|
-
|
55
|
+
error_msg = f"Error prompting user: {str(e)}"
|
56
|
+
print_error(error_msg, "Prompt Error")
|
57
|
+
return (error_msg, True)
|
janito/tools/replace_file.py
CHANGED
@@ -5,9 +5,12 @@ import os
|
|
5
5
|
from typing import Tuple
|
6
6
|
|
7
7
|
from janito.tools.decorators import tool
|
8
|
+
from janito.tools.rich_console import print_info, print_success, print_error
|
9
|
+
from janito.tools.usage_tracker import track_usage, get_tracker
|
8
10
|
|
9
11
|
|
10
12
|
@tool
|
13
|
+
@track_usage('files_modified')
|
11
14
|
def replace_file(file_path: str, new_content: str) -> Tuple[str, bool]:
|
12
15
|
"""
|
13
16
|
Replace an existing file with new content.
|
@@ -20,17 +23,41 @@ def replace_file(file_path: str, new_content: str) -> Tuple[str, bool]:
|
|
20
23
|
A tuple containing (message, is_error)
|
21
24
|
"""
|
22
25
|
try:
|
26
|
+
print_info(f"Replacing file '{file_path}'", "File Operation")
|
27
|
+
|
23
28
|
# Convert relative path to absolute path
|
24
29
|
abs_path = os.path.abspath(file_path)
|
25
30
|
|
26
31
|
# Check if file exists
|
27
32
|
if not os.path.isfile(abs_path):
|
28
|
-
|
33
|
+
error_msg = f"Error: File '{file_path}' does not exist"
|
34
|
+
print_error(error_msg, "File Error")
|
35
|
+
return error_msg, True
|
36
|
+
|
37
|
+
# Read the original content to calculate line delta
|
38
|
+
try:
|
39
|
+
with open(abs_path, 'r', encoding='utf-8') as f:
|
40
|
+
old_content = f.read()
|
41
|
+
|
42
|
+
# Calculate line delta
|
43
|
+
old_lines_count = len(old_content.splitlines()) if old_content else 0
|
44
|
+
new_lines_count = len(new_content.splitlines()) if new_content else 0
|
45
|
+
line_delta = new_lines_count - old_lines_count
|
46
|
+
|
47
|
+
# Track line delta
|
48
|
+
get_tracker().increment('lines_delta', line_delta)
|
49
|
+
except Exception:
|
50
|
+
# If we can't read the file, we can't calculate line delta
|
51
|
+
pass
|
29
52
|
|
30
53
|
# Write new content to the file
|
31
54
|
with open(abs_path, 'w', encoding='utf-8') as f:
|
32
55
|
f.write(new_content)
|
33
|
-
|
34
|
-
|
56
|
+
|
57
|
+
success_msg = f"Successfully replaced file '{file_path}'"
|
58
|
+
print_success(success_msg, "Success")
|
59
|
+
return success_msg, False
|
35
60
|
except Exception as e:
|
36
|
-
|
61
|
+
error_msg = f"Error replacing file '{file_path}': {str(e)}"
|
62
|
+
print_error(error_msg, "Error")
|
63
|
+
return error_msg, True
|
@@ -0,0 +1,176 @@
|
|
1
|
+
"""
|
2
|
+
Utility module for rich console printing in tools.
|
3
|
+
"""
|
4
|
+
from rich.console import Console
|
5
|
+
from rich.text import Text
|
6
|
+
from typing import Optional
|
7
|
+
from janito.config import get_config
|
8
|
+
|
9
|
+
# Create a shared console instance
|
10
|
+
console = Console()
|
11
|
+
|
12
|
+
def print_info(message: str, title: Optional[str] = None):
|
13
|
+
"""
|
14
|
+
Print an informational message with rich formatting.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
message: The message to print
|
18
|
+
title: Optional title for the panel
|
19
|
+
"""
|
20
|
+
# Skip printing if trust mode is enabled
|
21
|
+
if get_config().trust_mode:
|
22
|
+
return
|
23
|
+
# Map titles to specific icons
|
24
|
+
icon_map = {
|
25
|
+
# File operations
|
26
|
+
"Delete Operation": "🗑️ ",
|
27
|
+
"Move Operation": "📦",
|
28
|
+
"File Operation": "📄",
|
29
|
+
"Directory View": "📁",
|
30
|
+
"File View": "📄",
|
31
|
+
"File Creation": "📝",
|
32
|
+
"Undo Operation": "↩️",
|
33
|
+
|
34
|
+
# Search and find operations
|
35
|
+
"Text Search": "🔍",
|
36
|
+
"Search Results": "📊",
|
37
|
+
|
38
|
+
# Web operations
|
39
|
+
"Web Fetch": "🌐",
|
40
|
+
"Content Extraction": "📰",
|
41
|
+
"News Extraction": "📰",
|
42
|
+
"Targeted Extraction": "🎯",
|
43
|
+
"Content Chunking": "📊",
|
44
|
+
"Content Ex": "📰", # For truncated "Content Extraction" in search results
|
45
|
+
|
46
|
+
# Command execution
|
47
|
+
"Bash Run": "🔄",
|
48
|
+
|
49
|
+
# User interaction
|
50
|
+
"Input Instructions": "⌨️",
|
51
|
+
|
52
|
+
# Default
|
53
|
+
"Info": "ℹ️ ",
|
54
|
+
}
|
55
|
+
|
56
|
+
# Get the appropriate icon based on title and message content
|
57
|
+
icon = "ℹ️ " # Default icon
|
58
|
+
|
59
|
+
# Check for exact matches in the icon map based on title
|
60
|
+
if title and title in icon_map:
|
61
|
+
icon = icon_map[title]
|
62
|
+
else:
|
63
|
+
# Check for matching strings in both title and message
|
64
|
+
for key, value in icon_map.items():
|
65
|
+
# Skip the default "Info" key to avoid too many matches
|
66
|
+
if key == "Info":
|
67
|
+
continue
|
68
|
+
|
69
|
+
# Check if the key appears in both title and message (if title exists)
|
70
|
+
if title and key in title and key in message:
|
71
|
+
icon = value
|
72
|
+
break
|
73
|
+
|
74
|
+
# If no match found yet, check for partial matches for str_replace_editor operations
|
75
|
+
if title:
|
76
|
+
if "Replacing text in file" in title:
|
77
|
+
icon = "✏️ " # Edit icon
|
78
|
+
elif "Inserting text in file" in title:
|
79
|
+
icon = "➕" # Plus icon
|
80
|
+
elif "Viewing file" in title:
|
81
|
+
icon = "📄" # File icon
|
82
|
+
elif "Viewing directory" in title:
|
83
|
+
icon = "📁" # Directory icon
|
84
|
+
elif "Creating file" in title:
|
85
|
+
icon = "📝" # Create icon
|
86
|
+
elif "Undoing last edit" in title:
|
87
|
+
icon = "↩️" # Undo icon
|
88
|
+
|
89
|
+
# Add indentation to all tool messages
|
90
|
+
indent = " "
|
91
|
+
text = Text(message)
|
92
|
+
if title:
|
93
|
+
# Special case for Bash Run commands
|
94
|
+
if title == "Bash Run":
|
95
|
+
console.print("\n" + "-"*50)
|
96
|
+
console.print(f"{indent}{icon} {title}", style="bold white on blue")
|
97
|
+
console.print("-"*50)
|
98
|
+
console.print(f"{indent}$ {text}", style="white on dark_blue")
|
99
|
+
# Make sure we're not returning anything
|
100
|
+
return
|
101
|
+
else:
|
102
|
+
console.print(f"{indent}{icon} {message}", style="blue", end="")
|
103
|
+
else:
|
104
|
+
console.print(f"{indent}{icon} {text}", style="blue", end="")
|
105
|
+
|
106
|
+
def print_success(message: str, title: Optional[str] = None):
|
107
|
+
"""
|
108
|
+
Print a success message with rich formatting.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
message: The message to print
|
112
|
+
title: Optional title for the panel
|
113
|
+
"""
|
114
|
+
# Skip printing if trust mode is enabled
|
115
|
+
if get_config().trust_mode:
|
116
|
+
return
|
117
|
+
text = Text(message)
|
118
|
+
if title:
|
119
|
+
console.print(f" ✅ {message}", style="green")
|
120
|
+
else:
|
121
|
+
console.print(f"✅ {text}", style="green")
|
122
|
+
|
123
|
+
def print_error(message: str, title: Optional[str] = None):
|
124
|
+
"""
|
125
|
+
Print an error message with rich formatting.
|
126
|
+
In trust mode, error messages are suppressed.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
message: The message to print
|
130
|
+
title: Optional title for the panel
|
131
|
+
"""
|
132
|
+
# Skip printing if trust mode is enabled
|
133
|
+
if get_config().trust_mode:
|
134
|
+
return
|
135
|
+
|
136
|
+
text = Text(message)
|
137
|
+
|
138
|
+
# Check if message starts with question mark emoji (❓)
|
139
|
+
# If it does, use warning styling (yellow) instead of error styling (red)
|
140
|
+
starts_with_question_mark = message.startswith("❓")
|
141
|
+
|
142
|
+
if starts_with_question_mark:
|
143
|
+
# Use warning styling for question mark emoji errors
|
144
|
+
# For question mark emoji errors, don't include the title (like "Error")
|
145
|
+
# Just print the message with the emoji
|
146
|
+
if title == "File View":
|
147
|
+
console.print(f"\n {message}", style="yellow")
|
148
|
+
else:
|
149
|
+
console.print(f"{message}", style="yellow")
|
150
|
+
else:
|
151
|
+
# Regular error styling
|
152
|
+
if title:
|
153
|
+
# Special case for File View - print without header
|
154
|
+
if title == "File View":
|
155
|
+
console.print(f"\n ❌ {message}", style="red")
|
156
|
+
# Special case for Search Error
|
157
|
+
elif title == "Search Error":
|
158
|
+
console.print(f"❌ {message}", style="red")
|
159
|
+
else:
|
160
|
+
console.print(f"❌ {title} {text}", style="red")
|
161
|
+
else:
|
162
|
+
console.print(f"\n❌ {text}", style="red")
|
163
|
+
|
164
|
+
def print_warning(message: str):
|
165
|
+
"""
|
166
|
+
Print a warning message with rich formatting.
|
167
|
+
In trust mode, warning messages are suppressed.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
message: The message to print
|
171
|
+
"""
|
172
|
+
# Skip printing if trust mode is enabled
|
173
|
+
if get_config().trust_mode:
|
174
|
+
return
|
175
|
+
|
176
|
+
console.print(f"⚠️ {message}", style="yellow")
|