janito 0.15.0__py3-none-any.whl → 1.0.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.
Files changed (109) hide show
  1. janito/__init__.py +1 -5
  2. janito/__main__.py +3 -5
  3. janito/agent/__init__.py +1 -0
  4. janito/agent/agent.py +96 -0
  5. janito/agent/config.py +113 -0
  6. janito/agent/config_defaults.py +10 -0
  7. janito/agent/conversation.py +107 -0
  8. janito/agent/queued_tool_handler.py +16 -0
  9. janito/agent/runtime_config.py +30 -0
  10. janito/agent/tool_handler.py +124 -0
  11. janito/agent/tools/__init__.py +11 -0
  12. janito/agent/tools/ask_user.py +63 -0
  13. janito/agent/tools/bash_exec.py +58 -0
  14. janito/agent/tools/create_directory.py +19 -0
  15. janito/agent/tools/create_file.py +43 -0
  16. janito/agent/tools/fetch_url.py +48 -0
  17. janito/agent/tools/file_str_replace.py +48 -0
  18. janito/agent/tools/find_files.py +37 -0
  19. janito/agent/tools/gitignore_utils.py +40 -0
  20. janito/agent/tools/move_file.py +37 -0
  21. janito/agent/tools/remove_file.py +19 -0
  22. janito/agent/tools/rich_live.py +37 -0
  23. janito/agent/tools/rich_utils.py +31 -0
  24. janito/agent/tools/search_text.py +41 -0
  25. janito/agent/tools/view_file.py +34 -0
  26. janito/cli/__init__.py +0 -6
  27. janito/cli/_print_config.py +68 -0
  28. janito/cli/_utils.py +8 -0
  29. janito/cli/arg_parser.py +26 -0
  30. janito/cli/config_commands.py +131 -0
  31. janito/cli/logging_setup.py +27 -0
  32. janito/cli/main.py +39 -0
  33. janito/cli/runner.py +138 -0
  34. janito/cli_chat_shell/__init__.py +1 -0
  35. janito/cli_chat_shell/chat_loop.py +148 -0
  36. janito/cli_chat_shell/commands.py +202 -0
  37. janito/cli_chat_shell/config_shell.py +75 -0
  38. janito/cli_chat_shell/load_prompt.py +15 -0
  39. janito/cli_chat_shell/session_manager.py +60 -0
  40. janito/cli_chat_shell/ui.py +136 -0
  41. janito/render_prompt.py +12 -0
  42. janito/templates/system_instructions.j2 +38 -0
  43. janito/web/__init__.py +0 -0
  44. janito/web/__main__.py +17 -0
  45. janito/web/app.py +132 -0
  46. janito-1.0.1.dist-info/METADATA +144 -0
  47. janito-1.0.1.dist-info/RECORD +51 -0
  48. {janito-0.15.0.dist-info → janito-1.0.1.dist-info}/WHEEL +2 -1
  49. janito-1.0.1.dist-info/entry_points.txt +2 -0
  50. {janito-0.15.0.dist-info → janito-1.0.1.dist-info}/licenses/LICENSE +2 -2
  51. janito-1.0.1.dist-info/top_level.txt +1 -0
  52. janito/callbacks.py +0 -34
  53. janito/cli/agent/__init__.py +0 -7
  54. janito/cli/agent/conversation.py +0 -149
  55. janito/cli/agent/initialization.py +0 -168
  56. janito/cli/agent/query.py +0 -112
  57. janito/cli/agent.py +0 -12
  58. janito/cli/app.py +0 -178
  59. janito/cli/commands/__init__.py +0 -12
  60. janito/cli/commands/config.py +0 -30
  61. janito/cli/commands/history.py +0 -119
  62. janito/cli/commands/profile.py +0 -93
  63. janito/cli/commands/validation.py +0 -24
  64. janito/cli/commands/workspace.py +0 -31
  65. janito/cli/commands.py +0 -12
  66. janito/cli/output.py +0 -29
  67. janito/cli/utils.py +0 -22
  68. janito/config/README.md +0 -104
  69. janito/config/__init__.py +0 -16
  70. janito/config/cli/__init__.py +0 -28
  71. janito/config/cli/commands.py +0 -397
  72. janito/config/cli/validators.py +0 -77
  73. janito/config/core/__init__.py +0 -23
  74. janito/config/core/file_operations.py +0 -90
  75. janito/config/core/properties.py +0 -316
  76. janito/config/core/singleton.py +0 -282
  77. janito/config/profiles/__init__.py +0 -8
  78. janito/config/profiles/definitions.py +0 -38
  79. janito/config/profiles/manager.py +0 -80
  80. janito/data/instructions_template.txt +0 -34
  81. janito/token_report.py +0 -154
  82. janito/tools/__init__.py +0 -44
  83. janito/tools/bash/bash.py +0 -157
  84. janito/tools/bash/unix_persistent_bash.py +0 -215
  85. janito/tools/bash/win_persistent_bash.py +0 -341
  86. janito/tools/decorators.py +0 -90
  87. janito/tools/delete_file.py +0 -65
  88. janito/tools/fetch_webpage/__init__.py +0 -23
  89. janito/tools/fetch_webpage/core.py +0 -182
  90. janito/tools/find_files.py +0 -220
  91. janito/tools/move_file.py +0 -72
  92. janito/tools/prompt_user.py +0 -57
  93. janito/tools/replace_file.py +0 -63
  94. janito/tools/rich_console.py +0 -176
  95. janito/tools/search_text.py +0 -226
  96. janito/tools/str_replace_editor/__init__.py +0 -6
  97. janito/tools/str_replace_editor/editor.py +0 -55
  98. janito/tools/str_replace_editor/handlers/__init__.py +0 -16
  99. janito/tools/str_replace_editor/handlers/create.py +0 -60
  100. janito/tools/str_replace_editor/handlers/insert.py +0 -100
  101. janito/tools/str_replace_editor/handlers/str_replace.py +0 -94
  102. janito/tools/str_replace_editor/handlers/undo.py +0 -64
  103. janito/tools/str_replace_editor/handlers/view.py +0 -165
  104. janito/tools/str_replace_editor/utils.py +0 -33
  105. janito/tools/think.py +0 -37
  106. janito/tools/usage_tracker.py +0 -137
  107. janito-0.15.0.dist-info/METADATA +0 -481
  108. janito-0.15.0.dist-info/RECORD +0 -64
  109. janito-0.15.0.dist-info/entry_points.txt +0 -2
@@ -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")
@@ -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,6 +0,0 @@
1
- """
2
- Package for implementing the Claude text editor functionality.
3
- """
4
- from .editor import str_replace_editor
5
-
6
- __all__ = ["str_replace_editor"]
@@ -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)