janito 0.14.0__py3-none-any.whl → 1.0.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 -5
- janito/__main__.py +3 -5
- janito/agent/__init__.py +1 -0
- janito/agent/agent.py +96 -0
- janito/agent/config.py +113 -0
- janito/agent/config_defaults.py +10 -0
- janito/agent/conversation.py +107 -0
- janito/agent/queued_tool_handler.py +16 -0
- janito/agent/runtime_config.py +30 -0
- janito/agent/tool_handler.py +124 -0
- janito/agent/tools/__init__.py +11 -0
- janito/agent/tools/ask_user.py +63 -0
- janito/agent/tools/bash_exec.py +58 -0
- janito/agent/tools/create_directory.py +19 -0
- janito/agent/tools/create_file.py +43 -0
- janito/agent/tools/fetch_url.py +48 -0
- janito/agent/tools/file_str_replace.py +48 -0
- janito/agent/tools/find_files.py +37 -0
- janito/agent/tools/gitignore_utils.py +40 -0
- janito/agent/tools/move_file.py +37 -0
- janito/agent/tools/remove_file.py +19 -0
- janito/agent/tools/rich_live.py +37 -0
- janito/agent/tools/rich_utils.py +31 -0
- janito/agent/tools/search_text.py +41 -0
- janito/agent/tools/view_file.py +34 -0
- janito/cli/__init__.py +0 -6
- janito/cli/_print_config.py +68 -0
- janito/cli/_utils.py +8 -0
- janito/cli/arg_parser.py +26 -0
- janito/cli/config_commands.py +131 -0
- janito/cli/logging_setup.py +27 -0
- janito/cli/main.py +39 -0
- janito/cli/runner.py +135 -0
- janito/cli_chat_shell/__init__.py +1 -0
- janito/cli_chat_shell/chat_loop.py +147 -0
- janito/cli_chat_shell/commands.py +202 -0
- janito/cli_chat_shell/config_shell.py +75 -0
- janito/cli_chat_shell/load_prompt.py +15 -0
- janito/cli_chat_shell/session_manager.py +60 -0
- janito/cli_chat_shell/ui.py +136 -0
- janito/render_prompt.py +12 -0
- janito/templates/system_instructions.j2 +36 -0
- janito/web/__init__.py +0 -0
- janito/web/__main__.py +17 -0
- janito/web/app.py +132 -0
- janito-1.0.0.dist-info/METADATA +144 -0
- janito-1.0.0.dist-info/RECORD +51 -0
- {janito-0.14.0.dist-info → janito-1.0.0.dist-info}/WHEEL +2 -1
- janito-1.0.0.dist-info/entry_points.txt +2 -0
- {janito-0.14.0.dist-info → janito-1.0.0.dist-info}/licenses/LICENSE +2 -2
- janito-1.0.0.dist-info/top_level.txt +1 -0
- janito/callbacks.py +0 -34
- janito/cli/agent/__init__.py +0 -7
- janito/cli/agent/conversation.py +0 -149
- janito/cli/agent/initialization.py +0 -172
- janito/cli/agent/query.py +0 -108
- janito/cli/agent.py +0 -12
- janito/cli/app.py +0 -182
- janito/cli/commands/__init__.py +0 -12
- janito/cli/commands/config.py +0 -242
- janito/cli/commands/history.py +0 -119
- janito/cli/commands/profile.py +0 -72
- janito/cli/commands/validation.py +0 -24
- janito/cli/commands/workspace.py +0 -31
- janito/cli/commands.py +0 -12
- janito/cli/output.py +0 -29
- janito/cli/utils.py +0 -22
- janito/config.py +0 -375
- janito/data/instructions_template.txt +0 -31
- janito/token_report.py +0 -154
- janito/tools/__init__.py +0 -44
- janito/tools/bash/bash.py +0 -84
- janito/tools/bash/unix_persistent_bash.py +0 -184
- janito/tools/bash/win_persistent_bash.py +0 -308
- janito/tools/decorators.py +0 -90
- janito/tools/delete_file.py +0 -65
- janito/tools/fetch_webpage/__init__.py +0 -23
- janito/tools/fetch_webpage/core.py +0 -182
- janito/tools/find_files.py +0 -220
- janito/tools/move_file.py +0 -72
- janito/tools/prompt_user.py +0 -57
- janito/tools/replace_file.py +0 -63
- janito/tools/rich_console.py +0 -176
- janito/tools/search_text.py +0 -226
- janito/tools/str_replace_editor/__init__.py +0 -6
- janito/tools/str_replace_editor/editor.py +0 -55
- janito/tools/str_replace_editor/handlers/__init__.py +0 -16
- janito/tools/str_replace_editor/handlers/create.py +0 -60
- janito/tools/str_replace_editor/handlers/insert.py +0 -100
- janito/tools/str_replace_editor/handlers/str_replace.py +0 -94
- janito/tools/str_replace_editor/handlers/undo.py +0 -64
- janito/tools/str_replace_editor/handlers/view.py +0 -159
- janito/tools/str_replace_editor/utils.py +0 -33
- janito/tools/think.py +0 -37
- janito/tools/usage_tracker.py +0 -137
- janito-0.14.0.dist-info/METADATA +0 -396
- janito-0.14.0.dist-info/RECORD +0 -53
- janito-0.14.0.dist-info/entry_points.txt +0 -2
janito/tools/rich_console.py
DELETED
@@ -1,176 +0,0 @@
|
|
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")
|
janito/tools/search_text.py
DELETED
@@ -1,226 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import fnmatch
|
3
|
-
import re
|
4
|
-
import glob
|
5
|
-
from typing import List, Tuple
|
6
|
-
from janito.tools.rich_console import print_info, print_success, print_error, print_warning
|
7
|
-
from janito.tools.usage_tracker import track_usage
|
8
|
-
|
9
|
-
|
10
|
-
@track_usage('search_operations')
|
11
|
-
def search_text(text_pattern: str, file_pattern: str = "*", root_dir: str = ".", recursive: bool = True) -> Tuple[str, bool]:
|
12
|
-
"""
|
13
|
-
Search for text patterns within files matching a filename pattern.
|
14
|
-
Files in .gitignore are always ignored.
|
15
|
-
|
16
|
-
Args:
|
17
|
-
text_pattern: Text pattern to search for within files
|
18
|
-
file_pattern: Pattern to match file paths against (e.g., "*.py", "*/tools/*.py")
|
19
|
-
Multiple patterns can be specified using semicolons or spaces as separators
|
20
|
-
root_dir: Root directory to start search from (default: current directory)
|
21
|
-
recursive: Whether to search recursively in subdirectories (default: True)
|
22
|
-
|
23
|
-
Returns:
|
24
|
-
A tuple containing (message, is_error)
|
25
|
-
"""
|
26
|
-
# Simplified initial message
|
27
|
-
print_info(f"Searching for '{text_pattern}' in '{file_pattern}'", "Text Search")
|
28
|
-
try:
|
29
|
-
# Convert to absolute path if relative
|
30
|
-
abs_root = os.path.abspath(root_dir)
|
31
|
-
|
32
|
-
if not os.path.isdir(abs_root):
|
33
|
-
error_msg = f"Error: Directory '{root_dir}' does not exist"
|
34
|
-
print_error(error_msg, "Directory Error")
|
35
|
-
return error_msg, True
|
36
|
-
|
37
|
-
# Compile the regex pattern for better performance
|
38
|
-
try:
|
39
|
-
regex = re.compile(text_pattern)
|
40
|
-
except re.error:
|
41
|
-
# Simplified error message without the specific regex error details
|
42
|
-
error_msg = f"Error: Invalid regex pattern '{text_pattern}'"
|
43
|
-
print_error(error_msg, "Search Error")
|
44
|
-
return error_msg, True
|
45
|
-
|
46
|
-
matching_files = []
|
47
|
-
match_count = 0
|
48
|
-
results = []
|
49
|
-
|
50
|
-
# Get gitignore patterns
|
51
|
-
ignored_patterns = _get_gitignore_patterns(abs_root)
|
52
|
-
|
53
|
-
# Handle multiple patterns separated by semicolons or spaces
|
54
|
-
patterns = []
|
55
|
-
if ';' in file_pattern:
|
56
|
-
patterns = file_pattern.split(';')
|
57
|
-
elif ' ' in file_pattern and not (os.path.sep in file_pattern or '/' in file_pattern):
|
58
|
-
# Only split by space if the pattern doesn't appear to be a path
|
59
|
-
patterns = file_pattern.split()
|
60
|
-
else:
|
61
|
-
patterns = [file_pattern]
|
62
|
-
|
63
|
-
# Process each pattern
|
64
|
-
for pattern in patterns:
|
65
|
-
# Construct the glob pattern with the root directory
|
66
|
-
glob_pattern = os.path.join(abs_root, pattern) if not pattern.startswith(os.path.sep) else pattern
|
67
|
-
|
68
|
-
# Use recursive glob if needed
|
69
|
-
if recursive:
|
70
|
-
# Use ** pattern for recursive search if not already in the pattern
|
71
|
-
if '**' not in glob_pattern:
|
72
|
-
# Check if the pattern already has a directory component
|
73
|
-
if os.path.sep in pattern or '/' in pattern:
|
74
|
-
# Pattern already has directory component, keep as is
|
75
|
-
pass
|
76
|
-
else:
|
77
|
-
# Add ** to search in all subdirectories
|
78
|
-
glob_pattern = os.path.join(abs_root, '**', pattern)
|
79
|
-
|
80
|
-
# Use recursive=True for Python 3.5+ glob
|
81
|
-
glob_files = glob.glob(glob_pattern, recursive=True)
|
82
|
-
else:
|
83
|
-
# Non-recursive mode - only search in the specified directory
|
84
|
-
glob_files = glob.glob(glob_pattern)
|
85
|
-
|
86
|
-
# Process matching files
|
87
|
-
for file_path in glob_files:
|
88
|
-
# Skip directories and already processed files
|
89
|
-
if not os.path.isfile(file_path) or file_path in matching_files:
|
90
|
-
continue
|
91
|
-
|
92
|
-
# Skip ignored files
|
93
|
-
if _is_ignored(file_path, ignored_patterns, abs_root):
|
94
|
-
continue
|
95
|
-
|
96
|
-
file_matches = _search_file(file_path, regex, abs_root)
|
97
|
-
if file_matches:
|
98
|
-
matching_files.append(file_path)
|
99
|
-
match_count += len(file_matches)
|
100
|
-
results.append(f"\n{os.path.relpath(file_path, abs_root)} ({len(file_matches)} matches):")
|
101
|
-
results.extend(file_matches)
|
102
|
-
|
103
|
-
if matching_files:
|
104
|
-
# Only print the count summary, not the full results
|
105
|
-
summary = f"{match_count} matches in {len(matching_files)} files"
|
106
|
-
print_success(summary, "Search Results")
|
107
|
-
|
108
|
-
# Still return the full results for programmatic use
|
109
|
-
result_text = "\n".join(results)
|
110
|
-
result_msg = f"Searching for '{text_pattern}' in files matching '{file_pattern}':{result_text}\n{summary}"
|
111
|
-
return result_msg, False
|
112
|
-
else:
|
113
|
-
result_msg = f"No matches found for '{text_pattern}' in files matching '{file_pattern}'"
|
114
|
-
print_warning("No matches found.")
|
115
|
-
return result_msg, False
|
116
|
-
|
117
|
-
except Exception as e:
|
118
|
-
error_msg = f"Error searching text: {str(e)}"
|
119
|
-
print_error(error_msg, "Search Error")
|
120
|
-
return error_msg, True
|
121
|
-
|
122
|
-
|
123
|
-
def _search_file(file_path: str, pattern: re.Pattern, root_dir: str) -> List[str]:
|
124
|
-
"""
|
125
|
-
Search for regex pattern in a file and return matching lines with line numbers.
|
126
|
-
|
127
|
-
Args:
|
128
|
-
file_path: Path to the file to search
|
129
|
-
pattern: Compiled regex pattern to search for
|
130
|
-
root_dir: Root directory (for path display)
|
131
|
-
|
132
|
-
Returns:
|
133
|
-
List of formatted matches with line numbers and content
|
134
|
-
"""
|
135
|
-
matches = []
|
136
|
-
try:
|
137
|
-
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
138
|
-
for i, line in enumerate(f, 1):
|
139
|
-
if pattern.search(line):
|
140
|
-
# Truncate long lines for display
|
141
|
-
display_line = line.strip()
|
142
|
-
if len(display_line) > 100:
|
143
|
-
display_line = display_line[:97] + "..."
|
144
|
-
matches.append(f" Line {i}: {display_line}")
|
145
|
-
except (UnicodeDecodeError, IOError):
|
146
|
-
# Skip binary files or files with encoding issues
|
147
|
-
pass
|
148
|
-
return matches
|
149
|
-
|
150
|
-
|
151
|
-
def _get_gitignore_patterns(root_dir: str) -> List[str]:
|
152
|
-
"""
|
153
|
-
Get patterns from .gitignore files.
|
154
|
-
|
155
|
-
Args:
|
156
|
-
root_dir: Root directory to start from
|
157
|
-
|
158
|
-
Returns:
|
159
|
-
List of gitignore patterns
|
160
|
-
"""
|
161
|
-
patterns = []
|
162
|
-
|
163
|
-
# Check for .gitignore in the root directory
|
164
|
-
gitignore_path = os.path.join(root_dir, '.gitignore')
|
165
|
-
if os.path.isfile(gitignore_path):
|
166
|
-
try:
|
167
|
-
with open(gitignore_path, 'r', encoding='utf-8') as f:
|
168
|
-
for line in f:
|
169
|
-
line = line.strip()
|
170
|
-
# Skip empty lines and comments
|
171
|
-
if line and not line.startswith('#'):
|
172
|
-
patterns.append(line)
|
173
|
-
except Exception:
|
174
|
-
pass
|
175
|
-
|
176
|
-
# Add common patterns that are always ignored
|
177
|
-
common_patterns = [
|
178
|
-
'.git/', '.venv/', 'venv/', '__pycache__/', '*.pyc',
|
179
|
-
'*.pyo', '*.pyd', '.DS_Store', '*.so', '*.egg-info/'
|
180
|
-
]
|
181
|
-
patterns.extend(common_patterns)
|
182
|
-
|
183
|
-
return patterns
|
184
|
-
|
185
|
-
|
186
|
-
def _is_ignored(path: str, patterns: List[str], root_dir: str) -> bool:
|
187
|
-
"""
|
188
|
-
Check if a path should be ignored based on gitignore patterns.
|
189
|
-
|
190
|
-
Args:
|
191
|
-
path: Path to check
|
192
|
-
patterns: List of gitignore patterns
|
193
|
-
root_dir: Root directory for relative paths
|
194
|
-
|
195
|
-
Returns:
|
196
|
-
True if the path should be ignored, False otherwise
|
197
|
-
"""
|
198
|
-
# Get the relative path from the root directory
|
199
|
-
rel_path = os.path.relpath(path, root_dir)
|
200
|
-
|
201
|
-
# Convert to forward slashes for consistency with gitignore patterns
|
202
|
-
rel_path = rel_path.replace(os.sep, '/')
|
203
|
-
|
204
|
-
# Add trailing slash for directories
|
205
|
-
if os.path.isdir(path) and not rel_path.endswith('/'):
|
206
|
-
rel_path += '/'
|
207
|
-
|
208
|
-
for pattern in patterns:
|
209
|
-
# Handle negation patterns (those starting with !)
|
210
|
-
if pattern.startswith('!'):
|
211
|
-
continue # Skip negation patterns for simplicity
|
212
|
-
|
213
|
-
# Handle directory-specific patterns (those ending with /)
|
214
|
-
if pattern.endswith('/'):
|
215
|
-
if os.path.isdir(path) and fnmatch.fnmatch(rel_path, pattern + '*'):
|
216
|
-
return True
|
217
|
-
|
218
|
-
# Handle file patterns
|
219
|
-
if fnmatch.fnmatch(rel_path, pattern):
|
220
|
-
return True
|
221
|
-
|
222
|
-
# Handle patterns without wildcards as path prefixes
|
223
|
-
if '*' not in pattern and '?' not in pattern and rel_path.startswith(pattern):
|
224
|
-
return True
|
225
|
-
|
226
|
-
return False
|
@@ -1,55 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Main module for implementing the Claude text editor functionality.
|
3
|
-
"""
|
4
|
-
from typing import Tuple
|
5
|
-
from janito.config import get_config
|
6
|
-
from .handlers import (
|
7
|
-
handle_create,
|
8
|
-
handle_view,
|
9
|
-
handle_str_replace,
|
10
|
-
handle_insert,
|
11
|
-
handle_undo_edit
|
12
|
-
)
|
13
|
-
|
14
|
-
def str_replace_editor(**kwargs) -> Tuple[str, bool]:
|
15
|
-
"""
|
16
|
-
Custom editing tool for viewing, creating and editing files
|
17
|
-
* State is persistent across command calls and discussions with the user
|
18
|
-
* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep
|
19
|
-
* The `create` command cannot be used if the specified `path` already exists as a file
|
20
|
-
* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`
|
21
|
-
* The `undo_edit` command will revert the last edit made to the file at `path`
|
22
|
-
* When in ask mode, only the `view` command is allowed
|
23
|
-
|
24
|
-
Notes for using the `str_replace` command:
|
25
|
-
* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!
|
26
|
-
* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique
|
27
|
-
* The `new_str` parameter should contain the edited lines that should replace the `old_str`
|
28
|
-
|
29
|
-
Args:
|
30
|
-
**kwargs: All arguments passed to the tool, including:
|
31
|
-
- command: The command to execute (view, create, str_replace, insert, undo_edit)
|
32
|
-
- path: Path to the file
|
33
|
-
- Additional command-specific arguments
|
34
|
-
|
35
|
-
Returns:
|
36
|
-
A tuple containing (message, is_error)
|
37
|
-
"""
|
38
|
-
command = kwargs.get("command")
|
39
|
-
|
40
|
-
# If in ask mode, only allow view operations
|
41
|
-
if get_config().ask_mode and command != "view":
|
42
|
-
return ("Cannot perform file modifications in ask mode. Use --ask option to disable modifications.", True)
|
43
|
-
|
44
|
-
if command == "create":
|
45
|
-
return handle_create(kwargs)
|
46
|
-
elif command == "view":
|
47
|
-
return handle_view(kwargs)
|
48
|
-
elif command == "str_replace":
|
49
|
-
return handle_str_replace(kwargs)
|
50
|
-
elif command == "insert":
|
51
|
-
return handle_insert(kwargs)
|
52
|
-
elif command == "undo_edit":
|
53
|
-
return handle_undo_edit(kwargs)
|
54
|
-
else:
|
55
|
-
return (f"Command '{command}' not implemented yet", True)
|
@@ -1,16 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Package for str_replace_editor command handlers.
|
3
|
-
"""
|
4
|
-
from .create import handle_create
|
5
|
-
from .view import handle_view
|
6
|
-
from .str_replace import handle_str_replace
|
7
|
-
from .insert import handle_insert
|
8
|
-
from .undo import handle_undo_edit
|
9
|
-
|
10
|
-
__all__ = [
|
11
|
-
"handle_create",
|
12
|
-
"handle_view",
|
13
|
-
"handle_str_replace",
|
14
|
-
"handle_insert",
|
15
|
-
"handle_undo_edit"
|
16
|
-
]
|
@@ -1,60 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Handler for the create command in str_replace_editor.
|
3
|
-
"""
|
4
|
-
import os
|
5
|
-
import pathlib
|
6
|
-
from typing import Dict, Any, Tuple
|
7
|
-
from janito.config import get_config
|
8
|
-
from janito.tools.rich_console import print_info, print_success, print_error
|
9
|
-
from janito.tools.usage_tracker import get_tracker
|
10
|
-
from ..utils import normalize_path
|
11
|
-
|
12
|
-
def handle_create(args: Dict[str, Any]) -> Tuple[str, bool]:
|
13
|
-
"""
|
14
|
-
Create a new file with the specified content.
|
15
|
-
|
16
|
-
Args:
|
17
|
-
args: Dictionary containing:
|
18
|
-
- path: Path to the file to create
|
19
|
-
- file_text: Content to write to the file
|
20
|
-
|
21
|
-
Returns:
|
22
|
-
A tuple containing (message, is_error)
|
23
|
-
"""
|
24
|
-
path = args.get("path")
|
25
|
-
file_text = args.get("file_text", "")
|
26
|
-
|
27
|
-
# Count the number of lines in the file content
|
28
|
-
line_count = len(file_text.splitlines())
|
29
|
-
print_info(f"Creating file: {path} (+{line_count} lines)", "File Creation")
|
30
|
-
|
31
|
-
if not path:
|
32
|
-
return ("Missing required parameter: path", True)
|
33
|
-
|
34
|
-
path = normalize_path(path)
|
35
|
-
|
36
|
-
# Convert to Path object for better path handling
|
37
|
-
file_path = pathlib.Path(path)
|
38
|
-
|
39
|
-
# Check if the file already exists - according to spec, create cannot be used if file exists
|
40
|
-
if file_path.exists() and file_path.is_file():
|
41
|
-
print_error(f"File {path} already exists. The 'create' command cannot be used if the specified path already exists as a file.", "Error")
|
42
|
-
return (f"File {path} already exists. The 'create' command cannot be used if the specified path already exists as a file.", True)
|
43
|
-
|
44
|
-
# Create parent directories if they don't exist
|
45
|
-
file_path.parent.mkdir(parents=True, exist_ok=True)
|
46
|
-
|
47
|
-
# Write the content to the file
|
48
|
-
try:
|
49
|
-
with open(file_path, 'w', encoding='utf-8') as f:
|
50
|
-
f.write(file_text)
|
51
|
-
# Track file creation and line delta
|
52
|
-
get_tracker().increment('files_created')
|
53
|
-
get_tracker().increment('lines_delta', line_count)
|
54
|
-
# Show relative path if it's not an absolute path
|
55
|
-
display_path = path if os.path.isabs(path) else os.path.relpath(file_path, get_config().workspace_dir)
|
56
|
-
print_success(f"", "Success")
|
57
|
-
return (f"Successfully created file {display_path}", False)
|
58
|
-
except Exception as e:
|
59
|
-
print_error(f"Error creating file {path}: {str(e)}", "Error")
|
60
|
-
return (f"Error creating file {path}: {str(e)}", True)
|
@@ -1,100 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Handler for the insert command in str_replace_editor.
|
3
|
-
"""
|
4
|
-
import os
|
5
|
-
import pathlib
|
6
|
-
from typing import Dict, Any, Tuple
|
7
|
-
from janito.config import get_config
|
8
|
-
from janito.tools.rich_console import print_info, print_success, print_error
|
9
|
-
from janito.tools.usage_tracker import get_tracker
|
10
|
-
from ..utils import normalize_path, _file_history
|
11
|
-
|
12
|
-
def handle_insert(args: Dict[str, Any]) -> Tuple[str, bool]:
|
13
|
-
"""
|
14
|
-
Insert text at a specific location in a file.
|
15
|
-
|
16
|
-
Args:
|
17
|
-
args: Dictionary containing:
|
18
|
-
- path: Path to the file to modify
|
19
|
-
- insert_line: The line number after which to insert the text
|
20
|
-
- new_str: The text to insert
|
21
|
-
|
22
|
-
Returns:
|
23
|
-
A tuple containing (message, is_error)
|
24
|
-
"""
|
25
|
-
path = args.get("path")
|
26
|
-
insert_line = args.get("insert_line")
|
27
|
-
new_str = args.get("new_str")
|
28
|
-
|
29
|
-
# Count lines in new string
|
30
|
-
new_lines_count = len(new_str.splitlines()) if new_str else 0
|
31
|
-
|
32
|
-
print_info(f"Inserting text in file: {path}, after line {insert_line} (+{new_lines_count} lines)", "Insert Operation")
|
33
|
-
|
34
|
-
if not path:
|
35
|
-
print_error("Missing required parameter: path", "Error")
|
36
|
-
return ("Missing required parameter: path", True)
|
37
|
-
if insert_line is None:
|
38
|
-
print_error("Missing required parameter: insert_line", "Error")
|
39
|
-
return ("Missing required parameter: insert_line", True)
|
40
|
-
if new_str is None:
|
41
|
-
print_error("Missing required parameter: new_str", "Error")
|
42
|
-
return ("Missing required parameter: new_str", True)
|
43
|
-
|
44
|
-
# Store the original path for display purposes
|
45
|
-
original_path = path
|
46
|
-
|
47
|
-
# Normalize the path (converts to absolute path)
|
48
|
-
path = normalize_path(path)
|
49
|
-
file_path = pathlib.Path(path)
|
50
|
-
|
51
|
-
if not file_path.exists():
|
52
|
-
print_error(f"File {path} does not exist", "Error")
|
53
|
-
return (f"File {path} does not exist", True)
|
54
|
-
|
55
|
-
try:
|
56
|
-
# Read the file content
|
57
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
58
|
-
lines = f.readlines()
|
59
|
-
content = "".join(lines)
|
60
|
-
|
61
|
-
# Save the current content for undo
|
62
|
-
if path not in _file_history:
|
63
|
-
_file_history[path] = []
|
64
|
-
_file_history[path].append(content)
|
65
|
-
|
66
|
-
# Check if insert_line is valid
|
67
|
-
if insert_line < 0 or insert_line > len(lines):
|
68
|
-
print_error(f"Invalid insert line {insert_line} for file {path}", "Error")
|
69
|
-
return (f"Invalid insert line {insert_line} for file {path}", True)
|
70
|
-
|
71
|
-
# Ensure new_str ends with a newline if it doesn't already
|
72
|
-
if new_str and not new_str.endswith('\n'):
|
73
|
-
new_str += '\n'
|
74
|
-
|
75
|
-
# Insert the new string
|
76
|
-
lines.insert(insert_line, new_str)
|
77
|
-
|
78
|
-
# Track the number of lines inserted
|
79
|
-
lines_count = len(new_str.splitlines())
|
80
|
-
get_tracker().increment('lines_replaced', lines_count)
|
81
|
-
|
82
|
-
# Write the new content
|
83
|
-
with open(file_path, 'w', encoding='utf-8') as f:
|
84
|
-
f.writelines(lines)
|
85
|
-
|
86
|
-
# Show relative path if it's not an absolute path in the original input
|
87
|
-
display_path = original_path if os.path.isabs(original_path) else os.path.relpath(file_path, get_config().workspace_dir)
|
88
|
-
|
89
|
-
# If the response is too long, truncate it
|
90
|
-
response = f"Successfully inserted text at line {insert_line} in file {display_path}"
|
91
|
-
print_success(response, "Success")
|
92
|
-
if len(response) > 1000: # Arbitrary limit for demonstration
|
93
|
-
return (response[:1000] + "\n<response clipped>", False)
|
94
|
-
|
95
|
-
return (response, False)
|
96
|
-
except Exception as e:
|
97
|
-
display_path = original_path if os.path.isabs(original_path) else os.path.relpath(file_path, get_config().workspace_dir)
|
98
|
-
error_msg = f"Error inserting text in file {display_path}: {str(e)}"
|
99
|
-
print_error(error_msg, "Error")
|
100
|
-
return (error_msg, True)
|