janito 1.10.0__py3-none-any.whl → 1.11.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.
- janito/__init__.py +1 -1
- janito/agent/conversation_api.py +178 -90
- janito/agent/conversation_ui.py +1 -1
- janito/agent/llm_conversation_history.py +12 -0
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +19 -4
- janito/agent/tools/__init__.py +2 -0
- janito/agent/tools/create_directory.py +1 -1
- janito/agent/tools/create_file.py +1 -1
- janito/agent/tools/fetch_url.py +1 -1
- janito/agent/tools/find_files.py +26 -13
- janito/agent/tools/get_file_outline/core.py +1 -1
- janito/agent/tools/get_file_outline/python_outline.py +139 -95
- janito/agent/tools/get_lines.py +92 -63
- janito/agent/tools/move_file.py +58 -32
- janito/agent/tools/open_url.py +31 -0
- janito/agent/tools/python_command_runner.py +85 -86
- janito/agent/tools/python_file_runner.py +85 -86
- janito/agent/tools/python_stdin_runner.py +87 -88
- janito/agent/tools/remove_directory.py +1 -1
- janito/agent/tools/remove_file.py +1 -1
- janito/agent/tools/replace_file.py +2 -2
- janito/agent/tools/replace_text_in_file.py +193 -149
- janito/agent/tools/run_bash_command.py +1 -1
- janito/agent/tools/run_powershell_command.py +4 -0
- janito/agent/tools/search_text/__init__.py +1 -0
- janito/agent/tools/search_text/core.py +176 -0
- janito/agent/tools/search_text/match_lines.py +58 -0
- janito/agent/tools/search_text/pattern_utils.py +65 -0
- janito/agent/tools/search_text/traverse_directory.py +132 -0
- janito/agent/tools/validate_file_syntax/core.py +41 -30
- janito/agent/tools/validate_file_syntax/html_validator.py +21 -5
- janito/agent/tools/validate_file_syntax/markdown_validator.py +77 -34
- janito/agent/tools_utils/gitignore_utils.py +25 -2
- janito/agent/tools_utils/utils.py +7 -1
- janito/cli/config_commands.py +112 -109
- janito/shell/main.py +51 -8
- janito/shell/session/config.py +83 -75
- janito/shell/ui/interactive.py +97 -73
- janito/termweb/static/editor.css +32 -29
- janito/termweb/static/editor.css.bak +140 -22
- janito/termweb/static/editor.html +12 -7
- janito/termweb/static/editor.html.bak +16 -11
- janito/termweb/static/editor.js +94 -40
- janito/termweb/static/editor.js.bak +97 -65
- janito/termweb/static/index.html +1 -2
- janito/termweb/static/index.html.bak +1 -1
- janito/termweb/static/termweb.css +1 -22
- janito/termweb/static/termweb.css.bak +6 -4
- janito/termweb/static/termweb.js +0 -6
- janito/termweb/static/termweb.js.bak +1 -2
- {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/METADATA +1 -1
- {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/RECORD +56 -51
- {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/WHEEL +1 -1
- janito/agent/tools/search_text.py +0 -254
- {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/entry_points.txt +0 -0
- {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/licenses/LICENSE +0 -0
- {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/top_level.txt +0 -0
@@ -2,114 +2,158 @@ import re
|
|
2
2
|
from typing import List
|
3
3
|
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
def handle_assignment(idx, assign_match, outline):
|
6
|
+
var_name = assign_match.group(2)
|
7
|
+
var_type = "const" if var_name.isupper() else "var"
|
8
|
+
outline.append(
|
9
|
+
{
|
10
|
+
"type": var_type,
|
11
|
+
"name": var_name,
|
12
|
+
"start": idx + 1,
|
13
|
+
"end": idx + 1,
|
14
|
+
"parent": "",
|
15
|
+
"docstring": "",
|
16
|
+
}
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
def handle_main(idx, outline):
|
21
|
+
outline.append(
|
22
|
+
{
|
23
|
+
"type": "main",
|
24
|
+
"name": "__main__",
|
25
|
+
"start": idx + 1,
|
26
|
+
"end": idx + 1,
|
27
|
+
"parent": "",
|
28
|
+
"docstring": "",
|
29
|
+
}
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
def close_stack_objects(idx, indent, stack, obj_ranges):
|
34
|
+
while stack and indent < stack[-1][2]:
|
35
|
+
popped = stack.pop()
|
36
|
+
obj_ranges.append((popped[0], popped[1], popped[3], idx, popped[4], popped[2]))
|
37
|
+
|
38
|
+
|
39
|
+
def close_last_top_obj(idx, last_top_obj, stack, obj_ranges):
|
40
|
+
if last_top_obj and last_top_obj in stack:
|
41
|
+
stack.remove(last_top_obj)
|
42
|
+
obj_ranges.append(
|
43
|
+
(
|
44
|
+
last_top_obj[0],
|
45
|
+
last_top_obj[1],
|
46
|
+
last_top_obj[3],
|
47
|
+
idx,
|
48
|
+
last_top_obj[4],
|
49
|
+
last_top_obj[2],
|
50
|
+
)
|
51
|
+
)
|
52
|
+
return None
|
53
|
+
return last_top_obj
|
54
|
+
|
55
|
+
|
56
|
+
def handle_class(idx, class_match, indent, stack, last_top_obj):
|
57
|
+
name = class_match.group(2)
|
58
|
+
parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
|
59
|
+
obj = ("class", name, indent, idx + 1, parent)
|
60
|
+
stack.append(obj)
|
61
|
+
if indent == 0:
|
62
|
+
last_top_obj = obj
|
63
|
+
return last_top_obj
|
64
|
+
|
65
|
+
|
66
|
+
def handle_function(idx, func_match, indent, stack, last_top_obj):
|
67
|
+
name = func_match.group(2)
|
68
|
+
parent = ""
|
69
|
+
for s in reversed(stack):
|
70
|
+
if s[0] == "class" and indent > s[2]:
|
71
|
+
parent = s[1]
|
72
|
+
break
|
73
|
+
obj = ("function", name, indent, idx + 1, parent)
|
74
|
+
stack.append(obj)
|
75
|
+
if indent == 0:
|
76
|
+
last_top_obj = obj
|
77
|
+
return last_top_obj
|
78
|
+
|
79
|
+
|
80
|
+
def process_line(idx, line, regexes, stack, obj_ranges, outline, last_top_obj):
|
81
|
+
class_pat, func_pat, assign_pat, main_pat = regexes
|
82
|
+
class_match = class_pat.match(line)
|
83
|
+
func_match = func_pat.match(line)
|
84
|
+
assign_match = assign_pat.match(line)
|
85
|
+
indent = len(line) - len(line.lstrip())
|
86
|
+
# If a new top-level class or function starts, close the previous one
|
87
|
+
if (class_match or func_match) and indent == 0 and last_top_obj:
|
88
|
+
last_top_obj = close_last_top_obj(idx, last_top_obj, stack, obj_ranges)
|
89
|
+
if class_match:
|
90
|
+
last_top_obj = handle_class(idx, class_match, indent, stack, last_top_obj)
|
91
|
+
elif func_match:
|
92
|
+
last_top_obj = handle_function(idx, func_match, indent, stack, last_top_obj)
|
93
|
+
elif assign_match and indent == 0:
|
94
|
+
handle_assignment(idx, assign_match, outline)
|
95
|
+
main_match = main_pat.match(line)
|
96
|
+
if main_match:
|
97
|
+
handle_main(idx, outline)
|
98
|
+
close_stack_objects(idx, indent, stack, obj_ranges)
|
99
|
+
return last_top_obj
|
100
|
+
|
101
|
+
|
102
|
+
def build_outline_entry(obj, lines, outline):
|
103
|
+
obj_type, name, start, end, parent, indent = obj
|
104
|
+
# Determine if this is a method
|
105
|
+
if obj_type == "function" and parent:
|
106
|
+
outline_type = "method"
|
107
|
+
elif obj_type == "function":
|
108
|
+
outline_type = "function"
|
109
|
+
else:
|
110
|
+
outline_type = obj_type
|
111
|
+
docstring = extract_docstring(lines, start, end)
|
112
|
+
outline.append(
|
113
|
+
{
|
114
|
+
"type": outline_type,
|
115
|
+
"name": name,
|
116
|
+
"start": start,
|
117
|
+
"end": end,
|
118
|
+
"parent": parent,
|
119
|
+
"docstring": docstring,
|
120
|
+
}
|
121
|
+
)
|
122
|
+
|
123
|
+
|
124
|
+
def process_lines(lines, regexes):
|
10
125
|
outline = []
|
11
|
-
stack = []
|
12
|
-
obj_ranges = []
|
126
|
+
stack = []
|
127
|
+
obj_ranges = []
|
13
128
|
last_top_obj = None
|
14
129
|
for idx, line in enumerate(lines):
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
indent = len(line) - len(line.lstrip())
|
19
|
-
# If a new top-level class or function starts, close the previous one
|
20
|
-
if (class_match or func_match) and indent == 0 and last_top_obj:
|
21
|
-
# Only close if still open
|
22
|
-
if last_top_obj in stack:
|
23
|
-
stack.remove(last_top_obj)
|
24
|
-
obj_ranges.append(
|
25
|
-
(
|
26
|
-
last_top_obj[0],
|
27
|
-
last_top_obj[1],
|
28
|
-
last_top_obj[3],
|
29
|
-
idx,
|
30
|
-
last_top_obj[4],
|
31
|
-
last_top_obj[2],
|
32
|
-
)
|
33
|
-
)
|
34
|
-
last_top_obj = None
|
35
|
-
if class_match:
|
36
|
-
name = class_match.group(2)
|
37
|
-
parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
|
38
|
-
obj = ("class", name, indent, idx + 1, parent)
|
39
|
-
stack.append(obj)
|
40
|
-
if indent == 0:
|
41
|
-
last_top_obj = obj
|
42
|
-
elif func_match:
|
43
|
-
name = func_match.group(2)
|
44
|
-
parent = ""
|
45
|
-
for s in reversed(stack):
|
46
|
-
if s[0] == "class" and indent > s[2]:
|
47
|
-
parent = s[1]
|
48
|
-
break
|
49
|
-
obj = ("function", name, indent, idx + 1, parent)
|
50
|
-
stack.append(obj)
|
51
|
-
if indent == 0:
|
52
|
-
last_top_obj = obj
|
53
|
-
elif assign_match and indent == 0:
|
54
|
-
var_name = assign_match.group(2)
|
55
|
-
var_type = "const" if var_name.isupper() else "var"
|
56
|
-
outline.append(
|
57
|
-
{
|
58
|
-
"type": var_type,
|
59
|
-
"name": var_name,
|
60
|
-
"start": idx + 1,
|
61
|
-
"end": idx + 1,
|
62
|
-
"parent": "",
|
63
|
-
"docstring": "",
|
64
|
-
}
|
65
|
-
)
|
66
|
-
main_match = main_pat.match(line)
|
67
|
-
if main_match:
|
68
|
-
outline.append(
|
69
|
-
{
|
70
|
-
"type": "main",
|
71
|
-
"name": "__main__",
|
72
|
-
"start": idx + 1,
|
73
|
-
"end": idx + 1,
|
74
|
-
"parent": "",
|
75
|
-
"docstring": "",
|
76
|
-
}
|
77
|
-
)
|
78
|
-
while stack and indent < stack[-1][2]:
|
79
|
-
popped = stack.pop()
|
80
|
-
obj_ranges.append(
|
81
|
-
(popped[0], popped[1], popped[3], idx, popped[4], popped[2])
|
82
|
-
)
|
130
|
+
last_top_obj = process_line(
|
131
|
+
idx, line, regexes, stack, obj_ranges, outline, last_top_obj
|
132
|
+
)
|
83
133
|
# Close any remaining open objects
|
84
134
|
for popped in stack:
|
85
135
|
obj_ranges.append(
|
86
136
|
(popped[0], popped[1], popped[3], len(lines), popped[4], popped[2])
|
87
137
|
)
|
138
|
+
return outline, obj_ranges
|
139
|
+
|
88
140
|
|
89
|
-
|
141
|
+
def build_outline(obj_ranges, lines, outline):
|
90
142
|
for obj in obj_ranges:
|
91
|
-
|
92
|
-
# Determine if this is a method
|
93
|
-
if obj_type == "function" and parent:
|
94
|
-
outline_type = "method"
|
95
|
-
elif obj_type == "function":
|
96
|
-
outline_type = "function"
|
97
|
-
else:
|
98
|
-
outline_type = obj_type
|
99
|
-
docstring = extract_docstring(lines, start, end)
|
100
|
-
outline.append(
|
101
|
-
{
|
102
|
-
"type": outline_type,
|
103
|
-
"name": name,
|
104
|
-
"start": start,
|
105
|
-
"end": end,
|
106
|
-
"parent": parent,
|
107
|
-
"docstring": docstring,
|
108
|
-
}
|
109
|
-
)
|
143
|
+
build_outline_entry(obj, lines, outline)
|
110
144
|
return outline
|
111
145
|
|
112
146
|
|
147
|
+
def parse_python_outline(lines: List[str]):
|
148
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
149
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
150
|
+
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
151
|
+
main_pat = re.compile(r"^\s*if\s+__name__\s*==\s*[\'\"]__main__[\'\"]\s*:")
|
152
|
+
regexes = (class_pat, func_pat, assign_pat, main_pat)
|
153
|
+
outline, obj_ranges = process_lines(lines, regexes)
|
154
|
+
return build_outline(obj_ranges, lines, outline)
|
155
|
+
|
156
|
+
|
113
157
|
def extract_docstring(lines, start_idx, end_idx):
|
114
158
|
"""Extracts a docstring from lines[start_idx:end_idx] if present."""
|
115
159
|
for i in range(start_idx, min(end_idx, len(lines))):
|
janito/agent/tools/get_lines.py
CHANGED
@@ -22,18 +22,34 @@ class GetLinesTool(ToolBase):
|
|
22
22
|
- "---\nFile: /path/to/file.py | Lines: 1-10 (of 100)\n---\n<lines...>"
|
23
23
|
- "---\nFile: /path/to/file.py | All lines (total: 100 (all))\n---\n<all lines...>"
|
24
24
|
- "Error reading file: <error message>"
|
25
|
-
- "
|
25
|
+
- "\u2757 not found"
|
26
26
|
"""
|
27
27
|
|
28
28
|
def run(self, file_path: str, from_line: int = None, to_line: int = None) -> str:
|
29
29
|
from janito.agent.tools_utils.utils import display_path
|
30
30
|
|
31
31
|
disp_path = display_path(file_path)
|
32
|
+
self._report_read_info(disp_path, from_line, to_line)
|
33
|
+
try:
|
34
|
+
lines = self._read_file_lines(file_path)
|
35
|
+
selected, selected_len, total_lines = self._select_lines(
|
36
|
+
lines, from_line, to_line
|
37
|
+
)
|
38
|
+
self._report_success(selected_len, from_line, to_line, total_lines)
|
39
|
+
header = self._format_header(
|
40
|
+
disp_path, from_line, to_line, selected_len, total_lines
|
41
|
+
)
|
42
|
+
return header + "".join(selected)
|
43
|
+
except Exception as e:
|
44
|
+
return self._handle_read_error(e)
|
45
|
+
|
46
|
+
def _report_read_info(self, disp_path, from_line, to_line):
|
47
|
+
"""Report the info message for reading lines."""
|
32
48
|
if from_line and to_line:
|
33
49
|
self.report_info(
|
34
50
|
ActionType.READ,
|
35
51
|
tr(
|
36
|
-
"📖
|
52
|
+
"📖 Read file '{disp_path}' {from_line}-{to_line}",
|
37
53
|
disp_path=disp_path,
|
38
54
|
from_line=from_line,
|
39
55
|
to_line=to_line,
|
@@ -42,79 +58,92 @@ class GetLinesTool(ToolBase):
|
|
42
58
|
else:
|
43
59
|
self.report_info(
|
44
60
|
ActionType.READ,
|
45
|
-
tr("📖
|
61
|
+
tr("📖 Read file '{disp_path}'", disp_path=disp_path),
|
46
62
|
)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if from_line
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
elif to_line < total_lines:
|
69
|
-
self.report_success(
|
70
|
-
tr(
|
71
|
-
" ✅ {selected_len} {line_word} ({remaining} to end)",
|
72
|
-
selected_len=selected_len,
|
73
|
-
line_word=pluralize("line", selected_len),
|
74
|
-
remaining=total_lines - to_line,
|
75
|
-
)
|
76
|
-
)
|
77
|
-
else:
|
63
|
+
|
64
|
+
def _read_file_lines(self, file_path):
|
65
|
+
"""Read all lines from the file."""
|
66
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
67
|
+
return f.readlines()
|
68
|
+
|
69
|
+
def _select_lines(self, lines, from_line, to_line):
|
70
|
+
"""Select the requested lines and return them with their count and total lines."""
|
71
|
+
selected = lines[
|
72
|
+
(from_line - 1 if from_line else 0) : (to_line if to_line else None)
|
73
|
+
]
|
74
|
+
selected_len = len(selected)
|
75
|
+
total_lines = len(lines)
|
76
|
+
return selected, selected_len, total_lines
|
77
|
+
|
78
|
+
def _report_success(self, selected_len, from_line, to_line, total_lines):
|
79
|
+
"""Report the success message after reading lines."""
|
80
|
+
if from_line and to_line:
|
81
|
+
requested = to_line - from_line + 1
|
82
|
+
at_end = to_line >= total_lines or selected_len < requested
|
83
|
+
if at_end:
|
78
84
|
self.report_success(
|
79
85
|
tr(
|
80
|
-
"
|
86
|
+
" \u2705 {selected_len} {line_word} (end)",
|
81
87
|
selected_len=selected_len,
|
82
88
|
line_word=pluralize("line", selected_len),
|
83
89
|
)
|
84
90
|
)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
"
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
)
|
93
|
-
else:
|
94
|
-
header = tr(
|
95
|
-
"---\n{disp_path} {from_line}-{to_line} (of {total_lines})\n---\n",
|
96
|
-
disp_path=disp_path,
|
97
|
-
from_line=from_line,
|
98
|
-
to_line=to_line,
|
99
|
-
total_lines=total_lines,
|
91
|
+
elif to_line < total_lines:
|
92
|
+
self.report_success(
|
93
|
+
tr(
|
94
|
+
" \u2705 {selected_len} {line_word} ({remaining} to end)",
|
95
|
+
selected_len=selected_len,
|
96
|
+
line_word=pluralize("line", selected_len),
|
97
|
+
remaining=total_lines - to_line,
|
100
98
|
)
|
101
|
-
|
102
|
-
|
103
|
-
|
99
|
+
)
|
100
|
+
else:
|
101
|
+
self.report_success(
|
102
|
+
tr(
|
103
|
+
" \u2705 {selected_len} {line_word} (all)",
|
104
|
+
selected_len=selected_len,
|
105
|
+
line_word=pluralize("line", selected_len),
|
106
|
+
)
|
107
|
+
)
|
108
|
+
|
109
|
+
def _format_header(self, disp_path, from_line, to_line, selected_len, total_lines):
|
110
|
+
"""Format the header for the output."""
|
111
|
+
if from_line and to_line:
|
112
|
+
requested = to_line - from_line + 1
|
113
|
+
at_end = selected_len < requested or to_line >= total_lines
|
114
|
+
if at_end:
|
115
|
+
return tr(
|
116
|
+
"---\n{disp_path} {from_line}-{to_line} (end)\n---\n",
|
104
117
|
disp_path=disp_path,
|
105
118
|
from_line=from_line,
|
106
|
-
|
119
|
+
to_line=to_line,
|
107
120
|
)
|
108
121
|
else:
|
109
|
-
|
110
|
-
"---\n{disp_path}
|
122
|
+
return tr(
|
123
|
+
"---\n{disp_path} {from_line}-{to_line} (of {total_lines})\n---\n",
|
111
124
|
disp_path=disp_path,
|
125
|
+
from_line=from_line,
|
126
|
+
to_line=to_line,
|
112
127
|
total_lines=total_lines,
|
113
128
|
)
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
129
|
+
elif from_line:
|
130
|
+
return tr(
|
131
|
+
"---\n{disp_path} {from_line}-END (of {total_lines})\n---\n",
|
132
|
+
disp_path=disp_path,
|
133
|
+
from_line=from_line,
|
134
|
+
total_lines=total_lines,
|
135
|
+
)
|
136
|
+
else:
|
137
|
+
return tr(
|
138
|
+
"---\n{disp_path} All lines (total: {total_lines} (all))\n---\n",
|
139
|
+
disp_path=disp_path,
|
140
|
+
total_lines=total_lines,
|
141
|
+
)
|
142
|
+
|
143
|
+
def _handle_read_error(self, e):
|
144
|
+
"""Handle file read errors and report appropriately."""
|
145
|
+
if isinstance(e, FileNotFoundError):
|
146
|
+
self.report_error(tr("\u2757 not found"))
|
147
|
+
return tr("\u2757 not found")
|
148
|
+
self.report_error(tr(" \u274c Error: {error}", error=e))
|
149
|
+
return tr("Error reading file: {error}", error=e)
|
janito/agent/tools/move_file.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import shutil
|
3
3
|
from janito.agent.tool_registry import register_tool
|
4
|
-
|
5
|
-
# from janito.agent.tools_utils.expand_path import expand_path
|
6
4
|
from janito.agent.tools_utils.utils import display_path
|
7
5
|
from janito.agent.tool_base import ToolBase
|
8
6
|
from janito.agent.tools_utils.action_type import ActionType
|
@@ -32,16 +30,55 @@ class MoveFileTool(ToolBase):
|
|
32
30
|
) -> str:
|
33
31
|
original_src = src_path
|
34
32
|
original_dest = dest_path
|
35
|
-
src = src_path
|
36
|
-
dest = dest_path
|
33
|
+
src = src_path
|
34
|
+
dest = dest_path
|
37
35
|
disp_src = display_path(original_src)
|
38
36
|
disp_dest = display_path(original_dest)
|
39
37
|
backup_path = None
|
38
|
+
|
39
|
+
valid, is_src_file, is_src_dir, err_msg = self._validate_source(src, disp_src)
|
40
|
+
if not valid:
|
41
|
+
return err_msg
|
42
|
+
|
43
|
+
dest_result = self._handle_destination(dest, disp_dest, overwrite, backup)
|
44
|
+
if dest_result is not None:
|
45
|
+
backup_path, err_msg = dest_result
|
46
|
+
if err_msg:
|
47
|
+
return err_msg
|
48
|
+
|
49
|
+
try:
|
50
|
+
self.report_info(
|
51
|
+
ActionType.WRITE,
|
52
|
+
tr(
|
53
|
+
"📝 Moving from '{disp_src}' to '{disp_dest}' ...",
|
54
|
+
disp_src=disp_src,
|
55
|
+
disp_dest=disp_dest,
|
56
|
+
),
|
57
|
+
)
|
58
|
+
shutil.move(src, dest)
|
59
|
+
self.report_success(tr("✅ Move complete."))
|
60
|
+
msg = tr("✅ Move complete.")
|
61
|
+
if backup_path:
|
62
|
+
msg += tr(
|
63
|
+
" (backup at {backup_disp})",
|
64
|
+
backup_disp=display_path(backup_path),
|
65
|
+
)
|
66
|
+
return msg
|
67
|
+
except Exception as e:
|
68
|
+
self.report_error(tr("❌ Error moving: {error}", error=e))
|
69
|
+
return tr("❌ Error moving: {error}", error=e)
|
70
|
+
|
71
|
+
def _validate_source(self, src, disp_src):
|
40
72
|
if not os.path.exists(src):
|
41
73
|
self.report_error(
|
42
74
|
tr("❌ Source '{disp_src}' does not exist.", disp_src=disp_src)
|
43
75
|
)
|
44
|
-
return
|
76
|
+
return (
|
77
|
+
False,
|
78
|
+
False,
|
79
|
+
False,
|
80
|
+
tr("❌ Source '{disp_src}' does not exist.", disp_src=disp_src),
|
81
|
+
)
|
45
82
|
is_src_file = os.path.isfile(src)
|
46
83
|
is_src_dir = os.path.isdir(src)
|
47
84
|
if not (is_src_file or is_src_dir):
|
@@ -51,10 +88,19 @@ class MoveFileTool(ToolBase):
|
|
51
88
|
disp_src=disp_src,
|
52
89
|
)
|
53
90
|
)
|
54
|
-
return
|
55
|
-
|
56
|
-
|
91
|
+
return (
|
92
|
+
False,
|
93
|
+
False,
|
94
|
+
False,
|
95
|
+
tr(
|
96
|
+
"❌ Source path '{disp_src}' is neither a file nor a directory.",
|
97
|
+
disp_src=disp_src,
|
98
|
+
),
|
57
99
|
)
|
100
|
+
return True, is_src_file, is_src_dir, None
|
101
|
+
|
102
|
+
def _handle_destination(self, dest, disp_dest, overwrite, backup):
|
103
|
+
backup_path = None
|
58
104
|
if os.path.exists(dest):
|
59
105
|
if not overwrite:
|
60
106
|
self.report_error(
|
@@ -63,7 +109,7 @@ class MoveFileTool(ToolBase):
|
|
63
109
|
disp_dest=disp_dest,
|
64
110
|
)
|
65
111
|
)
|
66
|
-
return tr(
|
112
|
+
return None, tr(
|
67
113
|
"❗ Destination '{disp_dest}' already exists and overwrite is False.",
|
68
114
|
disp_dest=disp_dest,
|
69
115
|
)
|
@@ -83,27 +129,7 @@ class MoveFileTool(ToolBase):
|
|
83
129
|
self.report_error(
|
84
130
|
tr("❌ Error removing destination before move: {error}", error=e)
|
85
131
|
)
|
86
|
-
return tr(
|
87
|
-
|
88
|
-
self.report_info(
|
89
|
-
ActionType.WRITE,
|
90
|
-
tr(
|
91
|
-
"📝 Moving from '{disp_src}' to '{disp_dest}' ...",
|
92
|
-
disp_src=disp_src,
|
93
|
-
disp_dest=disp_dest,
|
94
|
-
),
|
95
|
-
)
|
96
|
-
shutil.move(src, dest)
|
97
|
-
self.report_success(tr("✅ Move complete."))
|
98
|
-
msg = tr("✅ Move complete.")
|
99
|
-
if backup_path:
|
100
|
-
msg += tr(
|
101
|
-
" (backup at {backup_disp})",
|
102
|
-
backup_disp=display_path(
|
103
|
-
original_dest + (".bak" if is_src_file else ".bak.zip")
|
104
|
-
),
|
132
|
+
return None, tr(
|
133
|
+
"❌ Error removing destination before move: {error}", error=e
|
105
134
|
)
|
106
|
-
|
107
|
-
except Exception as e:
|
108
|
-
self.report_error(tr("❌ Error moving: {error}", error=e))
|
109
|
-
return tr("❌ Error moving: {error}", error=e)
|
135
|
+
return backup_path, None
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import webbrowser
|
2
|
+
from janito.agent.tool_registry import register_tool
|
3
|
+
from janito.agent.tool_base import ToolBase
|
4
|
+
from janito.agent.tools_utils.action_type import ActionType
|
5
|
+
from janito.i18n import tr
|
6
|
+
|
7
|
+
|
8
|
+
@register_tool(name="open_url")
|
9
|
+
class OpenUrlTool(ToolBase):
|
10
|
+
"""
|
11
|
+
Open the supplied URL in the default web browser.
|
12
|
+
Args:
|
13
|
+
url (str): The URL to open.
|
14
|
+
Returns:
|
15
|
+
str: Status message indicating the result.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def run(self, url: str) -> str:
|
19
|
+
if not url.strip():
|
20
|
+
self.report_warning(tr("ℹ️ Empty URL provided."))
|
21
|
+
return tr("Warning: Empty URL provided. Operation skipped.")
|
22
|
+
self.report_info(ActionType.READ, tr("🌐 Opening URL '{url}' ...", url=url))
|
23
|
+
try:
|
24
|
+
webbrowser.open(url)
|
25
|
+
except Exception as err:
|
26
|
+
self.report_error(
|
27
|
+
tr("❗ Error opening URL: {url}: {err}", url=url, err=str(err))
|
28
|
+
)
|
29
|
+
return tr("Warning: Error opening URL: {url}: {err}", url=url, err=str(err))
|
30
|
+
self.report_success(tr("✅ URL opened in browser: {url}", url=url))
|
31
|
+
return tr("URL opened in browser: {url}", url=url)
|