janito 1.5.2__py3-none-any.whl → 1.6.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 +0 -1
- janito/agent/config.py +11 -10
- janito/agent/config_defaults.py +3 -2
- janito/agent/conversation.py +93 -119
- janito/agent/conversation_api.py +98 -0
- janito/agent/conversation_exceptions.py +12 -0
- janito/agent/conversation_tool_calls.py +22 -0
- janito/agent/conversation_ui.py +17 -0
- janito/agent/message_handler.py +8 -9
- janito/agent/{agent.py → openai_client.py} +48 -16
- janito/agent/openai_schema_generator.py +53 -37
- janito/agent/profile_manager.py +172 -0
- janito/agent/queued_message_handler.py +13 -14
- janito/agent/rich_live.py +32 -0
- janito/agent/rich_message_handler.py +64 -0
- janito/agent/runtime_config.py +6 -1
- janito/agent/{tools/tool_base.py → tool_base.py} +15 -8
- janito/agent/tool_registry.py +118 -132
- janito/agent/tools/__init__.py +41 -2
- janito/agent/tools/ask_user.py +43 -33
- janito/agent/tools/create_directory.py +18 -16
- janito/agent/tools/create_file.py +31 -36
- janito/agent/tools/fetch_url.py +23 -19
- janito/agent/tools/find_files.py +40 -36
- janito/agent/tools/get_file_outline.py +100 -22
- janito/agent/tools/get_lines.py +40 -32
- janito/agent/tools/gitignore_utils.py +9 -6
- janito/agent/tools/move_file.py +22 -13
- janito/agent/tools/py_compile_file.py +40 -0
- janito/agent/tools/remove_directory.py +34 -24
- janito/agent/tools/remove_file.py +22 -20
- janito/agent/tools/replace_file.py +51 -0
- janito/agent/tools/replace_text_in_file.py +69 -42
- janito/agent/tools/rich_live.py +9 -2
- janito/agent/tools/run_bash_command.py +155 -107
- janito/agent/tools/run_python_command.py +139 -0
- janito/agent/tools/search_files.py +51 -34
- janito/agent/tools/tools_utils.py +4 -2
- janito/agent/tools/utils.py +6 -2
- janito/cli/_print_config.py +42 -16
- janito/cli/_utils.py +1 -0
- janito/cli/arg_parser.py +182 -29
- janito/cli/config_commands.py +54 -22
- janito/cli/logging_setup.py +9 -3
- janito/cli/main.py +11 -10
- janito/cli/runner/__init__.py +2 -0
- janito/cli/runner/cli_main.py +148 -0
- janito/cli/runner/config.py +33 -0
- janito/cli/runner/formatting.py +12 -0
- janito/cli/runner/scan.py +44 -0
- janito/cli_chat_shell/__init__.py +0 -1
- janito/cli_chat_shell/chat_loop.py +71 -92
- janito/cli_chat_shell/chat_state.py +38 -0
- janito/cli_chat_shell/chat_ui.py +43 -0
- janito/cli_chat_shell/commands/__init__.py +45 -0
- janito/cli_chat_shell/commands/config.py +22 -0
- janito/cli_chat_shell/commands/history_reset.py +29 -0
- janito/cli_chat_shell/commands/session.py +48 -0
- janito/cli_chat_shell/commands/session_control.py +12 -0
- janito/cli_chat_shell/commands/system.py +73 -0
- janito/cli_chat_shell/commands/utility.py +29 -0
- janito/cli_chat_shell/config_shell.py +39 -10
- janito/cli_chat_shell/load_prompt.py +5 -2
- janito/cli_chat_shell/session_manager.py +24 -27
- janito/cli_chat_shell/ui.py +75 -40
- janito/rich_utils.py +15 -2
- janito/web/__main__.py +10 -2
- janito/web/app.py +88 -52
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/METADATA +76 -11
- janito-1.6.0.dist-info/RECORD +81 -0
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/WHEEL +1 -1
- janito/agent/rich_tool_handler.py +0 -43
- janito/agent/templates/system_instructions.j2 +0 -38
- janito/agent/tool_auto_imports.py +0 -5
- janito/agent/tools/append_text_to_file.py +0 -41
- janito/agent/tools/py_compile.py +0 -39
- janito/agent/tools/python_exec.py +0 -83
- janito/cli/runner.py +0 -137
- janito/cli_chat_shell/commands.py +0 -204
- janito/render_prompt.py +0 -13
- janito-1.5.2.dist-info/RECORD +0 -66
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/entry_points.txt +0 -0
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/top_level.txt +0 -0
janito/agent/tools/find_files.py
CHANGED
@@ -1,56 +1,60 @@
|
|
1
|
-
from janito.agent.
|
1
|
+
from janito.agent.tool_base import ToolBase
|
2
2
|
from janito.agent.tool_registry import register_tool
|
3
|
-
|
3
|
+
from janito.agent.tools.tools_utils import pluralize
|
4
4
|
|
5
5
|
import fnmatch
|
6
6
|
from janito.agent.tools.gitignore_utils import filter_ignored
|
7
7
|
|
8
|
+
|
8
9
|
@register_tool(name="find_files")
|
9
10
|
class FindFilesTool(ToolBase):
|
11
|
+
"""
|
12
|
+
Find files in one or more directories matching a pattern. Respects .gitignore.
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
recursive: Whether to search recursively in subdirectories. Defaults to False.
|
19
|
-
max_results: Maximum number of results to return. Defaults to 100.
|
20
|
-
Returns:
|
21
|
-
Newline-separated list of matching file paths. Example:
|
14
|
+
Args:
|
15
|
+
directories (list[str]): List of directories to search in.
|
16
|
+
pattern (str): File pattern to match. Uses Unix shell-style wildcards (fnmatch), e.g. '*.py', 'data_??.csv', '[a-z]*.txt'.
|
17
|
+
recursive (bool, optional): Whether to search recursively in subdirectories. Defaults to False.
|
18
|
+
max_depth (int, optional): Maximum directory depth to search (0 = only top-level). If None, unlimited. Defaults to None.
|
19
|
+
Returns:
|
20
|
+
str: Newline-separated list of matching file paths. Example:
|
22
21
|
"/path/to/file1.py\n/path/to/file2.py"
|
23
22
|
"Warning: Empty file pattern provided. Operation skipped."
|
24
|
-
|
23
|
+
"""
|
24
|
+
|
25
|
+
def call(
|
26
|
+
self,
|
27
|
+
directories: list[str],
|
28
|
+
pattern: str,
|
29
|
+
recursive: bool = False,
|
30
|
+
max_depth: int = None,
|
31
|
+
) -> str:
|
25
32
|
import os
|
33
|
+
|
26
34
|
if not pattern:
|
27
|
-
self.report_warning(
|
35
|
+
self.report_warning(
|
36
|
+
"⚠️ Warning: Empty file pattern provided. Operation skipped."
|
37
|
+
)
|
28
38
|
return "Warning: Empty file pattern provided. Operation skipped."
|
29
39
|
from janito.agent.tools.tools_utils import display_path
|
30
|
-
|
31
|
-
|
40
|
+
|
41
|
+
output = []
|
32
42
|
for directory in directories:
|
33
43
|
disp_path = display_path(directory)
|
34
44
|
self.report_info(f"🔍 Searching for files '{pattern}' in '{disp_path}'")
|
35
45
|
for root, dirs, files in os.walk(directory):
|
46
|
+
# Calculate depth
|
47
|
+
rel_path = os.path.relpath(root, directory)
|
48
|
+
depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
|
49
|
+
if max_depth is not None and depth > max_depth:
|
50
|
+
# Prune traversal
|
51
|
+
dirs[:] = []
|
52
|
+
continue
|
53
|
+
if not recursive and depth > 0:
|
54
|
+
# Only top-level if not recursive
|
55
|
+
break
|
36
56
|
dirs, files = filter_ignored(root, dirs, files)
|
37
57
|
for filename in fnmatch.filter(files, pattern):
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
if not recursive:
|
42
|
-
break
|
43
|
-
if len(matches) >= max_results:
|
44
|
-
break
|
45
|
-
|
46
|
-
warning = ""
|
47
|
-
if len(matches) >= max_results:
|
48
|
-
warning = "\n⚠️ Warning: Maximum result limit reached. Some matches may not be shown."
|
49
|
-
suffix = " (Max Reached)"
|
50
|
-
else:
|
51
|
-
suffix = ""
|
52
|
-
self.report_success(f" ✅ {len(matches)} {pluralize('file', len(matches))}{suffix}")
|
53
|
-
return "\n".join(matches) + warning
|
54
|
-
|
55
|
-
|
56
|
-
from janito.agent.tools.tools_utils import pluralize
|
58
|
+
output.append(os.path.join(root, filename))
|
59
|
+
self.report_success(f" ✅ {len(output)} {pluralize('file', len(output))} found")
|
60
|
+
return "\n".join(output)
|
@@ -1,39 +1,117 @@
|
|
1
|
-
from janito.agent.
|
1
|
+
from janito.agent.tool_base import ToolBase
|
2
2
|
from janito.agent.tool_registry import register_tool
|
3
|
-
|
4
|
-
|
3
|
+
import os
|
4
|
+
import re
|
5
|
+
from typing import List
|
5
6
|
|
6
7
|
|
7
8
|
@register_tool(name="get_file_outline")
|
8
9
|
class GetFileOutlineTool(ToolBase):
|
9
|
-
"""
|
10
|
-
|
11
|
-
"""
|
12
|
-
Get an outline of a file's structure.
|
10
|
+
"""
|
11
|
+
Get an outline of a file's structure.
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
Note:
|
14
|
+
The outline extraction for Python files is based on regular expression (regex) pattern matching for class and function definitions.
|
15
|
+
This approach may not capture all edge cases or non-standard code structures. For complex files, further examination or more advanced parsing may be required.
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
"
|
17
|
+
Args:
|
18
|
+
file_path (str): Path to the file.
|
19
|
+
Returns:
|
20
|
+
str: Outline of the file's structure, starting with a summary line. Example:
|
21
|
+
- "Outline: 5 items (python)\n| Type | Name | Start | End | Parent |\n|---------|-------------|-------|-----|----------|\n| class | MyClass | 1 | 20 | |\n| method | my_method | 3 | 10 | MyClass |\n| function| my_func | 22 | 30 | |\n..."
|
22
|
+
- "Outline: 100 lines (default)\nFile has 100 lines."
|
23
|
+
- "Error reading file: <error message>"
|
24
|
+
"""
|
25
|
+
|
26
|
+
def call(self, file_path: str) -> str:
|
22
27
|
from janito.agent.tools.tools_utils import display_path
|
28
|
+
|
23
29
|
disp_path = display_path(file_path)
|
24
30
|
self.report_info(f"📄 Getting outline for: {disp_path}")
|
25
31
|
|
26
32
|
try:
|
27
|
-
|
33
|
+
ext = os.path.splitext(file_path)[1].lower()
|
34
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
28
35
|
lines = f.readlines()
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
36
|
+
if ext == ".py":
|
37
|
+
outline_items = self._parse_python_outline(lines)
|
38
|
+
outline_type = "python"
|
39
|
+
table = self._format_outline_table(outline_items)
|
40
|
+
self.report_success(f"✅ {len(outline_items)} items ({outline_type})")
|
41
|
+
return f"Outline: {len(outline_items)} items ({outline_type})\n" + table
|
42
|
+
else:
|
43
|
+
outline_type = "default"
|
44
|
+
self.report_success(f"✅ {len(lines)} lines ({outline_type})")
|
45
|
+
return f"Outline: {len(lines)} lines ({outline_type})\nFile has {len(lines)} lines."
|
34
46
|
except Exception as e:
|
35
|
-
self.report_error(f"
|
47
|
+
self.report_error(f"❌ Error reading file: {e}")
|
36
48
|
return f"Error reading file: {e}"
|
37
49
|
|
50
|
+
def _parse_python_outline(self, lines: List[str]):
|
51
|
+
# Regex for class, function, and method definitions
|
52
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
53
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
54
|
+
outline = []
|
55
|
+
stack = [] # (name, type, indent, start, parent)
|
56
|
+
for idx, line in enumerate(lines):
|
57
|
+
class_match = class_pat.match(line)
|
58
|
+
func_match = func_pat.match(line)
|
59
|
+
indent = len(line) - len(line.lstrip())
|
60
|
+
if class_match:
|
61
|
+
name = class_match.group(2)
|
62
|
+
parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
|
63
|
+
stack.append(("class", name, indent, idx + 1, parent))
|
64
|
+
elif func_match:
|
65
|
+
name = func_match.group(2)
|
66
|
+
parent = (
|
67
|
+
stack[-1][1]
|
68
|
+
if stack
|
69
|
+
and stack[-1][0] in ("class", "function")
|
70
|
+
and indent > stack[-1][2]
|
71
|
+
else ""
|
72
|
+
)
|
73
|
+
stack.append(("function", name, indent, idx + 1, parent))
|
74
|
+
# Pop stack if indentation decreases
|
75
|
+
while stack and indent < stack[-1][2]:
|
76
|
+
popped = stack.pop()
|
77
|
+
outline.append(
|
78
|
+
{
|
79
|
+
"type": (
|
80
|
+
popped[0]
|
81
|
+
if popped[0] != "function" or popped[3] == 1
|
82
|
+
else ("method" if popped[4] else "function")
|
83
|
+
),
|
84
|
+
"name": popped[1],
|
85
|
+
# Add end line for popped item
|
86
|
+
"start": popped[3],
|
87
|
+
"end": idx,
|
88
|
+
"parent": popped[4],
|
89
|
+
}
|
90
|
+
)
|
91
|
+
# Pop any remaining items in the stack at EOF
|
92
|
+
for popped in stack:
|
93
|
+
outline.append(
|
94
|
+
{
|
95
|
+
"type": (
|
96
|
+
popped[0]
|
97
|
+
if popped[0] != "function" or popped[3] == 1
|
98
|
+
else ("method" if popped[4] else "function")
|
99
|
+
),
|
100
|
+
"name": popped[1],
|
101
|
+
"start": popped[3],
|
102
|
+
"end": len(lines),
|
103
|
+
"parent": popped[4],
|
104
|
+
}
|
105
|
+
)
|
106
|
+
return outline
|
38
107
|
|
39
|
-
|
108
|
+
def _format_outline_table(self, outline_items):
|
109
|
+
if not outline_items:
|
110
|
+
return "No classes or functions found."
|
111
|
+
header = "| Type | Name | Start | End | Parent |\n|---------|-------------|-------|-----|----------|"
|
112
|
+
rows = []
|
113
|
+
for item in outline_items:
|
114
|
+
rows.append(
|
115
|
+
f"| {item['type']:<7} | {item['name']:<11} | {item['start']:<5} | {item['end']:<3} | {item['parent']:<8} |"
|
116
|
+
)
|
117
|
+
return header + "\n" + "\n".join(rows)
|
janito/agent/tools/get_lines.py
CHANGED
@@ -1,28 +1,28 @@
|
|
1
|
-
from janito.agent.
|
1
|
+
from janito.agent.tool_base import ToolBase
|
2
2
|
from janito.agent.tool_registry import register_tool
|
3
|
-
|
3
|
+
from janito.agent.tools.tools_utils import pluralize
|
4
4
|
|
5
5
|
|
6
6
|
@register_tool(name="get_lines")
|
7
7
|
class GetLinesTool(ToolBase):
|
8
|
-
"""
|
9
|
-
|
10
|
-
"""
|
11
|
-
Get specific lines from a file.
|
8
|
+
"""
|
9
|
+
Read lines from a file. Returns specific lines if a range is provided, or the entire file if no range is given. If both from_line and to_line are None, the entire file is returned in one call—no need to chunk or split requests when reading the full file.
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
Args:
|
12
|
+
file_path (str): Path to the file to read lines from.
|
13
|
+
from_line (int, optional): Starting line number (1-based). If None, starts from the first line.
|
14
|
+
to_line (int, optional): Ending line number (1-based). If None, reads to the end of the file. If both are None, the entire file is returned.
|
15
|
+
Returns:
|
16
|
+
str: File content with a header indicating the file name and line range. Example:
|
17
|
+
- "---\nFile: /path/to/file.py | Lines: 1-10 (of 100)\n---\n<lines...>"
|
18
|
+
- "---\nFile: /path/to/file.py | All lines (total: 100)\n---\n<all lines...>"
|
19
|
+
- "Error reading file: <error message>"
|
20
|
+
- "❗ not found"
|
21
|
+
"""
|
17
22
|
|
18
|
-
|
19
|
-
str: File content with a header indicating the file name and line range. Example:
|
20
|
-
- "---\nFile: /path/to/file.py | Lines: 1-10 (of 100)\n---\n<lines...>"
|
21
|
-
- "---\nFile: /path/to/file.py | All lines (total: 100)\n---\n<all lines...>"
|
22
|
-
- "Error reading file: <error message>"
|
23
|
-
- "❗ not found"
|
24
|
-
"""
|
23
|
+
def call(self, file_path: str, from_line: int = None, to_line: int = None) -> str:
|
25
24
|
from janito.agent.tools.tools_utils import display_path
|
25
|
+
|
26
26
|
disp_path = display_path(file_path)
|
27
27
|
if from_line and to_line:
|
28
28
|
self.report_info(f"📄 Reading {disp_path} lines {from_line}-{to_line}")
|
@@ -30,39 +30,47 @@ class GetLinesTool(ToolBase):
|
|
30
30
|
self.report_info(f"📄 Reading {disp_path} (all lines)")
|
31
31
|
|
32
32
|
try:
|
33
|
-
with open(file_path,
|
33
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
34
34
|
lines = f.readlines()
|
35
|
-
selected = lines[
|
35
|
+
selected = lines[
|
36
|
+
(from_line - 1 if from_line else 0) : (to_line if to_line else None)
|
37
|
+
]
|
36
38
|
selected_len = len(selected)
|
37
39
|
total_lines = len(lines)
|
38
40
|
if from_line and to_line:
|
39
41
|
requested = to_line - from_line + 1
|
40
42
|
if selected_len < requested:
|
41
|
-
|
42
|
-
|
43
|
+
self.report_success(
|
44
|
+
f" ✅ {selected_len} {pluralize('line', selected_len)} (end at line {total_lines})"
|
45
|
+
)
|
43
46
|
elif to_line < total_lines:
|
44
|
-
|
45
|
-
|
47
|
+
self.report_success(
|
48
|
+
f" ✅ {selected_len} {pluralize('line', selected_len)} ({total_lines - to_line} lines to end)"
|
49
|
+
)
|
46
50
|
else:
|
47
|
-
|
48
|
-
|
51
|
+
self.report_success(
|
52
|
+
f" ✅ {selected_len} {pluralize('line', selected_len)} (end at line {total_lines})"
|
53
|
+
)
|
49
54
|
else:
|
50
|
-
|
51
|
-
|
55
|
+
self.report_success(
|
56
|
+
f" ✅ {selected_len} {pluralize('line', selected_len)} (full file)"
|
57
|
+
)
|
52
58
|
# Prepare header
|
53
59
|
if from_line and to_line:
|
54
60
|
header = f"---\nFile: {disp_path} | Lines: {from_line}-{to_line} (of {total_lines})\n---\n"
|
61
|
+
if to_line >= total_lines:
|
62
|
+
header = f"---\nFile: {disp_path} | Lines: {from_line}-{to_line} (end at line {total_lines})\n---\n"
|
55
63
|
elif from_line:
|
56
64
|
header = f"---\nFile: {disp_path} | Lines: {from_line}-END (of {total_lines})\n---\n"
|
65
|
+
header = f"---\nFile: {disp_path} | Lines: {from_line}-END (end at line {total_lines})\n---\n"
|
57
66
|
else:
|
58
|
-
header =
|
59
|
-
|
67
|
+
header = (
|
68
|
+
f"---\nFile: {disp_path} | All lines (total: {total_lines})\n---\n"
|
69
|
+
)
|
70
|
+
return header + "".join(selected)
|
60
71
|
except Exception as e:
|
61
72
|
if isinstance(e, FileNotFoundError):
|
62
73
|
self.report_error("❗ not found")
|
63
74
|
return "❗ not found"
|
64
75
|
self.report_error(f" ❌ Error: {e}")
|
65
76
|
return f"Error reading file: {e}"
|
66
|
-
|
67
|
-
|
68
|
-
from janito.agent.tools.tools_utils import pluralize
|
@@ -5,15 +5,15 @@ from janito.agent.tools.utils import expand_path
|
|
5
5
|
_spec = None
|
6
6
|
|
7
7
|
|
8
|
-
def load_gitignore_patterns(gitignore_path=
|
8
|
+
def load_gitignore_patterns(gitignore_path=".gitignore"):
|
9
9
|
global _spec
|
10
10
|
gitignore_path = expand_path(gitignore_path)
|
11
11
|
if not os.path.exists(gitignore_path):
|
12
|
-
_spec = pathspec.PathSpec.from_lines(
|
12
|
+
_spec = pathspec.PathSpec.from_lines("gitwildmatch", [])
|
13
13
|
return _spec
|
14
|
-
with open(gitignore_path,
|
14
|
+
with open(gitignore_path, "r", encoding="utf-8", errors="replace") as f:
|
15
15
|
lines = f.readlines()
|
16
|
-
_spec = pathspec.PathSpec.from_lines(
|
16
|
+
_spec = pathspec.PathSpec.from_lines("gitwildmatch", lines)
|
17
17
|
return _spec
|
18
18
|
|
19
19
|
|
@@ -23,7 +23,7 @@ def is_ignored(path):
|
|
23
23
|
if _spec is None:
|
24
24
|
_spec = load_gitignore_patterns()
|
25
25
|
# Normalize path to be relative and use forward slashes
|
26
|
-
rel_path = os.path.relpath(path).replace(os.sep,
|
26
|
+
rel_path = os.path.relpath(path).replace(os.sep, "/")
|
27
27
|
return _spec.match_file(rel_path)
|
28
28
|
|
29
29
|
|
@@ -35,7 +35,10 @@ def filter_ignored(root, dirs, files, spec=None):
|
|
35
35
|
spec = _spec
|
36
36
|
|
37
37
|
def not_ignored(p):
|
38
|
-
rel_path = os.path.relpath(os.path.join(root, p)).replace(os.sep,
|
38
|
+
rel_path = os.path.relpath(os.path.join(root, p)).replace(os.sep, "/")
|
39
|
+
# Always ignore .git directory (like git does)
|
40
|
+
if rel_path == ".git" or rel_path.startswith(".git/"):
|
41
|
+
return False
|
39
42
|
return not spec.match_file(rel_path)
|
40
43
|
|
41
44
|
dirs[:] = [d for d in dirs if not_ignored(d)]
|
janito/agent/tools/move_file.py
CHANGED
@@ -2,25 +2,30 @@ import os
|
|
2
2
|
import shutil
|
3
3
|
from janito.agent.tool_registry import register_tool
|
4
4
|
from janito.agent.tools.utils import expand_path, display_path
|
5
|
-
from janito.agent.
|
5
|
+
from janito.agent.tool_base import ToolBase
|
6
|
+
|
6
7
|
|
7
8
|
@register_tool(name="move_file")
|
8
9
|
class MoveFileTool(ToolBase):
|
9
10
|
"""
|
10
11
|
Move a file from src_path to dest_path.
|
11
|
-
"""
|
12
|
-
def call(self, src_path: str, dest_path: str, overwrite: bool = False) -> str:
|
13
|
-
"""
|
14
|
-
Move a file from src_path to dest_path.
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
Args:
|
14
|
+
src_path (str): Source file path.
|
15
|
+
dest_path (str): Destination file path.
|
16
|
+
overwrite (bool, optional): Whether to overwrite if the destination exists. Defaults to False.
|
17
|
+
backup (bool, optional): If True, create a backup (.bak) of the destination before moving if it exists. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
|
18
|
+
Returns:
|
19
|
+
str: Status message indicating the result.
|
20
|
+
"""
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
def call(
|
23
|
+
self,
|
24
|
+
src_path: str,
|
25
|
+
dest_path: str,
|
26
|
+
overwrite: bool = False,
|
27
|
+
backup: bool = False,
|
28
|
+
) -> str:
|
24
29
|
original_src = src_path
|
25
30
|
original_dest = dest_path
|
26
31
|
src = expand_path(src_path)
|
@@ -36,11 +41,15 @@ class MoveFileTool(ToolBase):
|
|
36
41
|
return f"\u274c Source path '{disp_src}' is not a file."
|
37
42
|
if os.path.exists(dest):
|
38
43
|
if not overwrite:
|
39
|
-
self.report_error(
|
44
|
+
self.report_error(
|
45
|
+
f"\u2757 Destination '{disp_dest}' exists and overwrite is False."
|
46
|
+
)
|
40
47
|
return f"\u2757 Destination '{disp_dest}' already exists and overwrite is False."
|
41
48
|
if os.path.isdir(dest):
|
42
49
|
self.report_error(f"\u274c Destination '{disp_dest}' is a directory.")
|
43
50
|
return f"\u274c Destination '{disp_dest}' is a directory."
|
51
|
+
if backup:
|
52
|
+
shutil.copy2(dest, dest + ".bak")
|
44
53
|
try:
|
45
54
|
shutil.move(src, dest)
|
46
55
|
self.report_success(f"\u2705 File moved from '{disp_src}' to '{disp_dest}'")
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from janito.agent.tool_base import ToolBase
|
2
|
+
from janito.agent.tool_registry import register_tool
|
3
|
+
|
4
|
+
from typing import Optional
|
5
|
+
import py_compile
|
6
|
+
|
7
|
+
|
8
|
+
@register_tool(name="py_compile_file")
|
9
|
+
class PyCompileFileTool(ToolBase):
|
10
|
+
"""
|
11
|
+
Validate a Python file by compiling it with py_compile.
|
12
|
+
Useful to validate python files after changing them, especially after import changes.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
file_path (str): Path to the Python file to compile.
|
16
|
+
doraise (bool, optional): Whether to raise exceptions on compilation errors. Defaults to True.
|
17
|
+
Returns:
|
18
|
+
str: Compilation status message. Example:
|
19
|
+
- "✅ Compiled"
|
20
|
+
- "Compile error: <error message>"
|
21
|
+
- "Error: <error message>"
|
22
|
+
"""
|
23
|
+
|
24
|
+
def call(self, file_path: str, doraise: Optional[bool] = True) -> str:
|
25
|
+
self.report_info(f"🛠️ Compiling Python file: {file_path}")
|
26
|
+
|
27
|
+
if not (file_path.endswith(".py") or file_path.endswith(".pyw")):
|
28
|
+
msg = f"Error: {file_path} is not a Python (.py/.pyw) file."
|
29
|
+
self.report_error(f" [py_compile_file] {msg}")
|
30
|
+
return msg
|
31
|
+
try:
|
32
|
+
py_compile.compile(file_path, doraise=doraise)
|
33
|
+
self.report_success(" ✅ Compiled")
|
34
|
+
return "✅ Compiled"
|
35
|
+
except py_compile.PyCompileError as e:
|
36
|
+
self.report_error(f" [py_compile_file] Compile error: {e}")
|
37
|
+
return f"Compile error: {e}"
|
38
|
+
except Exception as e:
|
39
|
+
self.report_error(f" [py_compile_file] Error: {e}")
|
40
|
+
return f"Error: {e}"
|
@@ -1,40 +1,50 @@
|
|
1
|
-
from janito.agent.
|
1
|
+
from janito.agent.tool_base import ToolBase
|
2
2
|
from janito.agent.tool_registry import register_tool
|
3
|
+
from janito.agent.tools.tools_utils import pluralize
|
3
4
|
|
4
5
|
import shutil
|
5
6
|
import os
|
6
|
-
|
7
|
+
import zipfile
|
7
8
|
|
8
9
|
|
9
10
|
@register_tool(name="remove_directory")
|
10
11
|
class RemoveDirectoryTool(ToolBase):
|
11
|
-
"""
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
self
|
26
|
-
|
12
|
+
"""
|
13
|
+
Remove a directory. If recursive=False and directory not empty, raises error.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
directory (str): Path to the directory to remove.
|
17
|
+
recursive (bool, optional): Remove recursively if True. Defaults to False.
|
18
|
+
backup (bool, optional): If True, create a backup (.bak.zip) before removing. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
|
19
|
+
Returns:
|
20
|
+
str: Status message indicating result. Example:
|
21
|
+
- "Directory removed: /path/to/dir"
|
22
|
+
- "Error removing directory: <error message>"
|
23
|
+
"""
|
24
|
+
|
25
|
+
def call(
|
26
|
+
self, directory: str, recursive: bool = False, backup: bool = False
|
27
|
+
) -> str:
|
28
|
+
self.report_info(
|
29
|
+
f"\U0001f5c3\ufe0f Removing directory: {directory} (recursive={recursive})"
|
30
|
+
)
|
27
31
|
try:
|
32
|
+
if backup and os.path.exists(directory) and os.path.isdir(directory):
|
33
|
+
backup_zip = directory.rstrip("/\\") + ".bak.zip"
|
34
|
+
with zipfile.ZipFile(backup_zip, "w", zipfile.ZIP_DEFLATED) as zipf:
|
35
|
+
for root, dirs, files in os.walk(directory):
|
36
|
+
for file in files:
|
37
|
+
abs_path = os.path.join(root, file)
|
38
|
+
rel_path = os.path.relpath(
|
39
|
+
abs_path, os.path.dirname(directory)
|
40
|
+
)
|
41
|
+
zipf.write(abs_path, rel_path)
|
28
42
|
if recursive:
|
29
43
|
shutil.rmtree(directory)
|
30
44
|
else:
|
31
45
|
os.rmdir(directory)
|
32
|
-
|
33
|
-
self.report_success(f"✅ 1 {pluralize('directory', 1)}")
|
46
|
+
self.report_success(f"\u2705 1 {pluralize('directory', 1)}")
|
34
47
|
return f"Directory removed: {directory}"
|
35
48
|
except Exception as e:
|
36
|
-
self.report_error(f"
|
49
|
+
self.report_error(f" \u274c Error removing directory: {e}")
|
37
50
|
return f"Error removing directory: {e}"
|
38
|
-
|
39
|
-
|
40
|
-
from janito.agent.tools.tools_utils import pluralize
|
@@ -1,38 +1,40 @@
|
|
1
1
|
import os
|
2
|
+
import shutil
|
2
3
|
from janito.agent.tool_registry import register_tool
|
3
4
|
from janito.agent.tools.utils import expand_path, display_path
|
4
|
-
from janito.agent.
|
5
|
+
from janito.agent.tool_base import ToolBase
|
6
|
+
|
5
7
|
|
6
8
|
@register_tool(name="remove_file")
|
7
9
|
class RemoveFileTool(ToolBase):
|
8
10
|
"""
|
9
11
|
Remove a file at the specified path.
|
10
|
-
"""
|
11
|
-
def call(self, file_path: str) -> str:
|
12
|
-
"""
|
13
|
-
Remove a file from the filesystem.
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
Args:
|
14
|
+
file_path (str): Path to the file to remove.
|
15
|
+
backup (bool, optional): If True, create a backup (.bak) before removing. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
|
16
|
+
Returns:
|
17
|
+
str: Status message indicating the result. Example:
|
18
|
+
- "\u2705 Successfully removed the file at ..."
|
19
|
+
- "\u2757 Cannot remove file: ..."
|
20
|
+
"""
|
17
21
|
|
18
|
-
|
19
|
-
str: Status message indicating the result. Example:
|
20
|
-
- "✅ Successfully removed the file at ..."
|
21
|
-
- "❗ Cannot remove file: ..."
|
22
|
-
"""
|
22
|
+
def call(self, file_path: str, backup: bool = False) -> str:
|
23
23
|
original_path = file_path
|
24
24
|
path = expand_path(file_path)
|
25
25
|
disp_path = display_path(original_path, path)
|
26
26
|
if not os.path.exists(path):
|
27
|
-
self.report_error(f"
|
28
|
-
return f"
|
27
|
+
self.report_error(f"\u274c File '{disp_path}' does not exist.")
|
28
|
+
return f"\u274c File '{disp_path}' does not exist."
|
29
29
|
if not os.path.isfile(path):
|
30
|
-
self.report_error(f"
|
31
|
-
return f"
|
30
|
+
self.report_error(f"\u274c Path '{disp_path}' is not a file.")
|
31
|
+
return f"\u274c Path '{disp_path}' is not a file."
|
32
32
|
try:
|
33
|
+
if backup:
|
34
|
+
shutil.copy2(path, path + ".bak")
|
33
35
|
os.remove(path)
|
34
|
-
self.report_success(f"
|
35
|
-
return f"
|
36
|
+
self.report_success(f"\u2705 File removed: '{disp_path}'")
|
37
|
+
return f"\u2705 Successfully removed the file at '{disp_path}'."
|
36
38
|
except Exception as e:
|
37
|
-
self.report_error(f"
|
38
|
-
return f"
|
39
|
+
self.report_error(f"\u274c Error removing file: {e}")
|
40
|
+
return f"\u274c Error removing file: {e}"
|