janito 3.4.0__py3-none-any.whl → 3.5.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/README.md +3 -0
- janito/cli/chat_mode/bindings.py +50 -0
- janito/cli/chat_mode/session.py +12 -1
- janito/cli/chat_mode/shell/commands/multi.py +5 -0
- janito/cli/chat_mode/shell/commands/security/allowed_sites.py +47 -33
- janito/cli/cli_commands/check_tools.py +212 -0
- janito/cli/cli_commands/list_plugins.py +52 -43
- janito/cli/core/getters.py +3 -0
- janito/cli/core/model_guesser.py +40 -24
- janito/cli/main_cli.py +9 -12
- janito/cli/prompt_core.py +47 -9
- janito/cli/rich_terminal_reporter.py +2 -2
- janito/drivers/openai/driver.py +1 -0
- janito/drivers/zai/driver.py +1 -0
- janito/i18n/it.py +46 -46
- janito/llm/agent.py +32 -16
- janito/llm/auth_utils.py +14 -5
- janito/llm/cancellation_manager.py +63 -0
- janito/llm/driver.py +8 -0
- janito/llm/enter_cancellation.py +107 -0
- janito/plugin_system/__init__.py +10 -0
- janito/{plugins → plugin_system}/base.py +5 -2
- janito/plugin_system/core_loader.py +217 -0
- janito/plugin_system/core_loader_fixed.py +225 -0
- janito/plugins/__init__.py +31 -12
- janito/plugins/auto_loader.py +12 -11
- janito/plugins/auto_loader_fixed.py +12 -11
- janito/plugins/builtin.py +15 -1
- janito/plugins/core/__init__.py +7 -0
- janito/plugins/core/codeanalyzer/__init__.py +43 -0
- janito/plugins/core/filemanager/__init__.py +124 -0
- janito/plugins/core/filemanager/tools/create_file.py +87 -0
- janito/plugins/core/filemanager/tools/replace_text_in_file.py +270 -0
- janito/plugins/core/imagedisplay/__init__.py +14 -0
- janito/plugins/core/imagedisplay/plugin.py +51 -0
- janito/plugins/core/imagedisplay/tools/__init__.py +1 -0
- janito/plugins/core/imagedisplay/tools/show_image.py +83 -0
- janito/{tools/adapters/local → plugins/core/imagedisplay/tools}/show_image_grid.py +13 -5
- janito/plugins/core/system/__init__.py +23 -0
- janito/plugins/core/system/tools/run_bash_command.py +204 -0
- janito/plugins/core/system/tools/run_powershell_command.py +234 -0
- janito/plugins/core_adapter.py +89 -11
- janito/plugins/dev/__init__.py +7 -0
- janito/plugins/dev/pythondev/__init__.py +37 -0
- janito/plugins/dev/visualization/__init__.py +23 -0
- janito/plugins/discovery.py +5 -5
- janito/plugins/discovery_core.py +14 -9
- janito/plugins/example_plugin.py +108 -0
- janito/plugins/manager.py +1 -1
- janito/plugins/tools/__init__.py +10 -0
- janito/{tools/adapters/local → plugins/tools}/ask_user.py +3 -3
- janito/plugins/tools/copy_file.py +87 -0
- janito/plugins/tools/core_tools_plugin.py +87 -0
- janito/plugins/tools/create_directory.py +70 -0
- janito/{tools/adapters/local → plugins/tools}/create_file.py +6 -6
- janito/plugins/tools/decorators.py +19 -0
- janito/plugins/tools/delete_text_in_file.py +134 -0
- janito/{tools/adapters/local → plugins/tools}/fetch_url.py +3 -3
- janito/plugins/tools/find_files.py +143 -0
- janito/plugins/tools/get_file_outline/__init__.py +7 -0
- janito/plugins/tools/get_file_outline/core.py +122 -0
- janito/plugins/tools/get_file_outline/java_outline.py +47 -0
- janito/plugins/tools/get_file_outline/markdown_outline.py +14 -0
- janito/plugins/tools/get_file_outline/python_outline.py +303 -0
- janito/plugins/tools/get_file_outline/search_outline.py +36 -0
- janito/plugins/tools/move_file.py +131 -0
- janito/plugins/tools/open_html_in_browser.py +51 -0
- janito/plugins/tools/open_url.py +37 -0
- janito/{tools/adapters/local → plugins/tools}/python_code_run.py +23 -7
- janito/{tools/adapters/local → plugins/tools}/python_command_run.py +21 -5
- janito/{tools/adapters/local → plugins/tools}/python_file_run.py +21 -5
- janito/plugins/tools/read_chart.py +259 -0
- janito/plugins/tools/read_files.py +58 -0
- janito/plugins/tools/remove_directory.py +55 -0
- janito/plugins/tools/remove_file.py +58 -0
- janito/{tools/adapters/local → plugins/tools}/replace_text_in_file.py +4 -4
- janito/{tools/adapters/local → plugins/tools}/run_bash_command.py +3 -3
- janito/{tools/adapters/local → plugins/tools}/run_powershell_command.py +3 -3
- janito/plugins/tools/search_text/__init__.py +7 -0
- janito/plugins/tools/search_text/core.py +205 -0
- janito/plugins/tools/search_text/match_lines.py +67 -0
- janito/plugins/tools/search_text/pattern_utils.py +73 -0
- janito/plugins/tools/search_text/traverse_directory.py +145 -0
- janito/{tools/adapters/local → plugins/tools}/show_image.py +15 -6
- janito/plugins/tools/show_image_grid.py +85 -0
- janito/plugins/tools/validate_file_syntax/__init__.py +7 -0
- janito/plugins/tools/validate_file_syntax/core.py +114 -0
- janito/plugins/tools/validate_file_syntax/css_validator.py +35 -0
- janito/plugins/tools/validate_file_syntax/html_validator.py +100 -0
- janito/plugins/tools/validate_file_syntax/jinja2_validator.py +50 -0
- janito/plugins/tools/validate_file_syntax/js_validator.py +27 -0
- janito/plugins/tools/validate_file_syntax/json_validator.py +6 -0
- janito/plugins/tools/validate_file_syntax/markdown_validator.py +109 -0
- janito/plugins/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/plugins/tools/validate_file_syntax/python_validator.py +5 -0
- janito/plugins/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/plugins/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/plugins/tools/view_file.py +172 -0
- janito/plugins/ui/__init__.py +7 -0
- janito/plugins/ui/userinterface/__init__.py +16 -0
- janito/plugins/ui/userinterface/tools/ask_user.py +110 -0
- janito/plugins/web/__init__.py +7 -0
- janito/plugins/web/webtools/__init__.py +33 -0
- janito/plugins/web/webtools/tools/fetch_url.py +458 -0
- janito/providers/__init__.py +1 -0
- janito/providers/together/__init__.py +1 -0
- janito/providers/together/model_info.py +69 -0
- janito/providers/together/provider.py +108 -0
- janito/tools/__init__.py +31 -7
- janito/tools/adapters/__init__.py +6 -1
- janito/tools/adapters/local/__init__.py +7 -70
- janito/tools/cli_initializer.py +88 -0
- janito/tools/initialize.py +70 -0
- janito/tools/loop_protection_decorator.py +114 -117
- janito-3.5.1.dist-info/METADATA +229 -0
- {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/RECORD +155 -86
- janito/plugins/core_loader.py +0 -120
- janito/plugins/core_loader_fixed.py +0 -125
- janito/tools/function_adapter.py +0 -65
- janito-3.4.0.dist-info/METADATA +0 -84
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/__init__.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/core.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/java_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/markdown_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/python_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/search_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/__init__.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/core.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/match_lines.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/pattern_utils.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/traverse_directory.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/copy_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/create_directory.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/delete_text_in_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/find_files.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/move_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/read_files.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_directory.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/__init__.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/core.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/css_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/html_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/jinja2_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/js_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/json_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/markdown_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/ps1_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/python_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/xml_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/yaml_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/view_file.py +0 -0
- /janito/{tools/adapters/local → plugins/dev/visualization/tools}/read_chart.py +0 -0
- /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_html_in_browser.py +0 -0
- /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_url.py +0 -0
- {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/WHEEL +0 -0
- {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/entry_points.txt +0 -0
- {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/licenses/LICENSE +0 -0
- {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,303 @@
|
|
1
|
+
import re
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
|
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 extract_signature_and_decorators(lines, start_idx):
|
103
|
+
"""
|
104
|
+
Extracts the signature line and leading decorators for a given function/class/method.
|
105
|
+
Returns (signature:str, decorators:List[str], signature_lineno:int)
|
106
|
+
"""
|
107
|
+
decorators = []
|
108
|
+
sig_line = None
|
109
|
+
sig_lineno = start_idx
|
110
|
+
for i in range(start_idx - 1, -1, -1):
|
111
|
+
striped = lines[i].strip()
|
112
|
+
if striped.startswith("@"):
|
113
|
+
decorators.insert(0, striped)
|
114
|
+
sig_lineno = i
|
115
|
+
elif not striped:
|
116
|
+
continue
|
117
|
+
else:
|
118
|
+
break
|
119
|
+
# Find the signature line itself
|
120
|
+
for k in range(start_idx, len(lines)):
|
121
|
+
striped = lines[k].strip()
|
122
|
+
if striped.startswith("def ") or striped.startswith("class "):
|
123
|
+
sig_line = striped
|
124
|
+
sig_lineno = k
|
125
|
+
break
|
126
|
+
return sig_line, decorators, sig_lineno
|
127
|
+
|
128
|
+
|
129
|
+
def extract_docstring(lines, start_idx, end_idx):
|
130
|
+
"""Extracts a docstring from lines[start_idx:end_idx] if present."""
|
131
|
+
for i in range(start_idx, min(end_idx, len(lines))):
|
132
|
+
line = lines[i].lstrip()
|
133
|
+
if not line:
|
134
|
+
continue
|
135
|
+
if line.startswith('"""') or line.startswith("'''"):
|
136
|
+
quote = line[:3]
|
137
|
+
doc = line[3:]
|
138
|
+
if doc.strip().endswith(quote):
|
139
|
+
return doc.strip()[:-3].strip()
|
140
|
+
docstring_lines = [doc]
|
141
|
+
for j in range(i + 1, min(end_idx, len(lines))):
|
142
|
+
line = lines[j]
|
143
|
+
if line.strip().endswith(quote):
|
144
|
+
docstring_lines.append(line.strip()[:-3])
|
145
|
+
return "\n".join([d.strip() for d in docstring_lines]).strip()
|
146
|
+
docstring_lines.append(line)
|
147
|
+
break
|
148
|
+
else:
|
149
|
+
break
|
150
|
+
return ""
|
151
|
+
|
152
|
+
|
153
|
+
def build_outline_entry(obj, lines, outline):
|
154
|
+
obj_type, name, start, end, parent, indent = obj
|
155
|
+
# Determine if this is a method
|
156
|
+
if obj_type == "function" and parent:
|
157
|
+
outline_type = "method"
|
158
|
+
elif obj_type == "function":
|
159
|
+
outline_type = "function"
|
160
|
+
else:
|
161
|
+
outline_type = obj_type
|
162
|
+
docstring = extract_docstring(lines, start, end)
|
163
|
+
outline.append(
|
164
|
+
{
|
165
|
+
"type": outline_type,
|
166
|
+
"name": name,
|
167
|
+
"start": start,
|
168
|
+
"end": end,
|
169
|
+
"parent": parent,
|
170
|
+
"docstring": docstring,
|
171
|
+
}
|
172
|
+
)
|
173
|
+
|
174
|
+
|
175
|
+
def process_lines(lines, regexes):
|
176
|
+
outline = []
|
177
|
+
stack = []
|
178
|
+
obj_ranges = []
|
179
|
+
last_top_obj = None
|
180
|
+
for idx, line in enumerate(lines):
|
181
|
+
last_top_obj = process_line(
|
182
|
+
idx, line, regexes, stack, obj_ranges, outline, last_top_obj
|
183
|
+
)
|
184
|
+
# Close any remaining open objects
|
185
|
+
for popped in stack:
|
186
|
+
obj_ranges.append(
|
187
|
+
(popped[0], popped[1], popped[3], len(lines), popped[4], popped[2])
|
188
|
+
)
|
189
|
+
return outline, obj_ranges
|
190
|
+
|
191
|
+
|
192
|
+
def build_outline(obj_ranges, lines, outline):
|
193
|
+
for obj in obj_ranges:
|
194
|
+
build_outline_entry(obj, lines, outline)
|
195
|
+
return outline
|
196
|
+
|
197
|
+
|
198
|
+
def parse_python_outline(lines: List[str]):
|
199
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
200
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
201
|
+
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
202
|
+
main_pat = re.compile(r"^\s*if\s+__name__\s*==\s*[\'\"]__main__[\'\"]\s*:")
|
203
|
+
outline = []
|
204
|
+
stack = []
|
205
|
+
obj_ranges = []
|
206
|
+
last_top_obj = None
|
207
|
+
for idx, line in enumerate(lines):
|
208
|
+
class_match = class_pat.match(line)
|
209
|
+
func_match = func_pat.match(line)
|
210
|
+
assign_match = assign_pat.match(line)
|
211
|
+
indent = len(line) - len(line.lstrip())
|
212
|
+
parent = ""
|
213
|
+
for s in reversed(stack):
|
214
|
+
if s[0] == "class" and indent > s[2]:
|
215
|
+
parent = s[1]
|
216
|
+
break
|
217
|
+
if class_match:
|
218
|
+
obj = ("class", class_match.group(2), idx + 1, None, parent, indent)
|
219
|
+
stack.append(obj)
|
220
|
+
last_top_obj = obj
|
221
|
+
elif func_match:
|
222
|
+
obj = ("function", func_match.group(2), idx + 1, None, parent, indent)
|
223
|
+
stack.append(obj)
|
224
|
+
last_top_obj = obj
|
225
|
+
elif assign_match and indent == 0:
|
226
|
+
outline.append(
|
227
|
+
{
|
228
|
+
"type": "const" if assign_match.group(2).isupper() else "var",
|
229
|
+
"name": assign_match.group(2),
|
230
|
+
"start": idx + 1,
|
231
|
+
"end": idx + 1,
|
232
|
+
"parent": "",
|
233
|
+
"signature": line.strip(),
|
234
|
+
"decorators": [],
|
235
|
+
"docstring": "",
|
236
|
+
}
|
237
|
+
)
|
238
|
+
if line.strip().startswith("if __name__ == "):
|
239
|
+
outline.append(
|
240
|
+
{
|
241
|
+
"type": "main",
|
242
|
+
"name": "__main__",
|
243
|
+
"start": idx + 1,
|
244
|
+
"end": idx + 1,
|
245
|
+
"parent": "",
|
246
|
+
"signature": line.strip(),
|
247
|
+
"decorators": [],
|
248
|
+
"docstring": "",
|
249
|
+
}
|
250
|
+
)
|
251
|
+
# Close stack objects if indent falls back
|
252
|
+
while stack and indent <= stack[-1][5] and idx + 1 > stack[-1][2]:
|
253
|
+
finished = stack.pop()
|
254
|
+
outline_entry = finished[:2] + (
|
255
|
+
finished[2],
|
256
|
+
idx + 1,
|
257
|
+
finished[4],
|
258
|
+
finished[5],
|
259
|
+
)
|
260
|
+
build_outline_entry(outline_entry, lines, outline)
|
261
|
+
# Close any remaining objects
|
262
|
+
while stack:
|
263
|
+
finished = stack.pop()
|
264
|
+
outline_entry = finished[:2] + (
|
265
|
+
finished[2],
|
266
|
+
len(lines),
|
267
|
+
finished[4],
|
268
|
+
finished[5],
|
269
|
+
)
|
270
|
+
build_outline_entry(outline_entry, lines, outline)
|
271
|
+
return outline
|
272
|
+
|
273
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
274
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
275
|
+
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
276
|
+
main_pat = re.compile(r"^\s*if\s+__name__\s*==\s*[\'\"]__main__[\'\"]\s*:")
|
277
|
+
regexes = (class_pat, func_pat, assign_pat, main_pat)
|
278
|
+
outline, obj_ranges = process_lines(lines, regexes)
|
279
|
+
return build_outline(obj_ranges, lines, outline)
|
280
|
+
|
281
|
+
|
282
|
+
def extract_docstring(lines, start_idx, end_idx):
|
283
|
+
"""Extracts a docstring from lines[start_idx:end_idx] if present."""
|
284
|
+
for i in range(start_idx, min(end_idx, len(lines))):
|
285
|
+
line = lines[i].lstrip()
|
286
|
+
if not line:
|
287
|
+
continue
|
288
|
+
if line.startswith('"""') or line.startswith("'''"):
|
289
|
+
quote = line[:3]
|
290
|
+
doc = line[3:]
|
291
|
+
if doc.strip().endswith(quote):
|
292
|
+
return doc.strip()[:-3].strip()
|
293
|
+
docstring_lines = [doc]
|
294
|
+
for j in range(i + 1, min(end_idx, len(lines))):
|
295
|
+
line = lines[j]
|
296
|
+
if line.strip().endswith(quote):
|
297
|
+
docstring_lines.append(line.strip()[:-3])
|
298
|
+
return "\n".join([d.strip() for d in docstring_lines]).strip()
|
299
|
+
docstring_lines.append(line)
|
300
|
+
break
|
301
|
+
else:
|
302
|
+
break
|
303
|
+
return ""
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
2
|
+
from janito.report_events import ReportAction
|
3
|
+
from janito.tools.loop_protection_decorator import protect_against_loops
|
4
|
+
|
5
|
+
|
6
|
+
class SearchOutlineTool(ToolBase):
|
7
|
+
"""
|
8
|
+
Tool for searching outlines in files.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
path (str): Path to the file for which to generate an outline.
|
12
|
+
Returns:
|
13
|
+
str: Outline search result or status message.
|
14
|
+
"""
|
15
|
+
|
16
|
+
permissions = ToolPermissions(read=True)
|
17
|
+
tool_name = "search_outline"
|
18
|
+
|
19
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
20
|
+
def run(self, path: str) -> str:
|
21
|
+
from janito.tools.tool_utils import display_path
|
22
|
+
from janito.i18n import tr
|
23
|
+
|
24
|
+
self.report_action(
|
25
|
+
tr(
|
26
|
+
"🔍 Searching for outline in '{disp_path}'",
|
27
|
+
disp_path=display_path(path),
|
28
|
+
),
|
29
|
+
ReportAction.READ,
|
30
|
+
)
|
31
|
+
# ... rest of implementation ...
|
32
|
+
# Example warnings and successes:
|
33
|
+
# self.report_warning(tr("No files found with supported extensions."))
|
34
|
+
# self.report_warning(tr("Error reading {path}: {error}", path=path, error=e))
|
35
|
+
# self.report_success(tr("✅ {count} {match_word} found", count=len(output), match_word=pluralize('match', len(output))))
|
36
|
+
pass
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import os
|
2
|
+
from janito.tools.path_utils import expand_path
|
3
|
+
import shutil
|
4
|
+
from janito.plugins.tools.decorators import register_core_tool
|
5
|
+
from janito.tools.tool_utils import display_path
|
6
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
7
|
+
from janito.report_events import ReportAction
|
8
|
+
from janito.i18n import tr
|
9
|
+
|
10
|
+
|
11
|
+
@register_core_tool
|
12
|
+
class MoveFile(ToolBase):
|
13
|
+
"""
|
14
|
+
Move a file or directory from src_path to dest_path.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
src_path (str): Source file or directory path.
|
18
|
+
dest_path (str): Destination file or directory path.
|
19
|
+
overwrite (bool, optional): Whether to overwrite if the destination exists. Defaults to False.
|
20
|
+
backup (bool, optional): Deprecated. No backups are created anymore. This flag is ignored. Defaults to False.
|
21
|
+
Returns:
|
22
|
+
str: Status message indicating the result.
|
23
|
+
"""
|
24
|
+
|
25
|
+
permissions = ToolPermissions(read=True, write=True)
|
26
|
+
tool_name = "move_file"
|
27
|
+
|
28
|
+
def run(
|
29
|
+
self,
|
30
|
+
src_path: str,
|
31
|
+
dest_path: str,
|
32
|
+
overwrite: bool = False,
|
33
|
+
backup: bool = False,
|
34
|
+
) -> str:
|
35
|
+
src = expand_path(src_path)
|
36
|
+
dest = expand_path(dest_path)
|
37
|
+
original_src = src_path
|
38
|
+
original_dest = dest_path
|
39
|
+
disp_src = display_path(original_src)
|
40
|
+
disp_dest = display_path(original_dest)
|
41
|
+
backup_path = None
|
42
|
+
|
43
|
+
valid, is_src_file, is_src_dir, err_msg = self._validate_source(src, disp_src)
|
44
|
+
if not valid:
|
45
|
+
return err_msg
|
46
|
+
|
47
|
+
dest_result = self._handle_destination(dest, disp_dest, overwrite, backup)
|
48
|
+
if dest_result is not None:
|
49
|
+
backup_path, err_msg = dest_result
|
50
|
+
if err_msg:
|
51
|
+
return err_msg
|
52
|
+
|
53
|
+
try:
|
54
|
+
self.report_action(
|
55
|
+
tr(
|
56
|
+
"📝 Moving from '{disp_src}' to '{disp_dest}' ...",
|
57
|
+
disp_src=disp_src,
|
58
|
+
disp_dest=disp_dest,
|
59
|
+
),
|
60
|
+
ReportAction.UPDATE,
|
61
|
+
)
|
62
|
+
shutil.move(src, dest)
|
63
|
+
self.report_success(tr("✅ Move complete."))
|
64
|
+
msg = tr("✅ Move complete.")
|
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):
|
72
|
+
if not os.path.exists(src):
|
73
|
+
self.report_error(
|
74
|
+
tr("❌ Source '{disp_src}' does not exist.", disp_src=disp_src)
|
75
|
+
)
|
76
|
+
return (
|
77
|
+
False,
|
78
|
+
False,
|
79
|
+
False,
|
80
|
+
tr("❌ Source '{disp_src}' does not exist.", disp_src=disp_src),
|
81
|
+
)
|
82
|
+
is_src_file = os.path.isfile(src)
|
83
|
+
is_src_dir = os.path.isdir(src)
|
84
|
+
if not (is_src_file or is_src_dir):
|
85
|
+
self.report_error(
|
86
|
+
tr(
|
87
|
+
"❌ Source path '{disp_src}' is neither a file nor a directory.",
|
88
|
+
disp_src=disp_src,
|
89
|
+
)
|
90
|
+
)
|
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
|
+
),
|
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
|
104
|
+
if os.path.exists(dest):
|
105
|
+
if not overwrite:
|
106
|
+
self.report_error(
|
107
|
+
tr(
|
108
|
+
"❗ Destination '{disp_dest}' exists and overwrite is False.",
|
109
|
+
disp_dest=disp_dest,
|
110
|
+
),
|
111
|
+
ReportAction.UPDATE,
|
112
|
+
)
|
113
|
+
return None, tr(
|
114
|
+
"❗ Destination '{disp_dest}' already exists and overwrite is False.",
|
115
|
+
disp_dest=disp_dest,
|
116
|
+
)
|
117
|
+
|
118
|
+
try:
|
119
|
+
if os.path.isfile(dest):
|
120
|
+
os.remove(dest)
|
121
|
+
elif os.path.isdir(dest):
|
122
|
+
shutil.rmtree(dest)
|
123
|
+
except Exception as e:
|
124
|
+
self.report_error(
|
125
|
+
tr("❌ Error removing destination before move: {error}", error=e),
|
126
|
+
ReportAction.UPDATE,
|
127
|
+
)
|
128
|
+
return None, tr(
|
129
|
+
"❌ Error removing destination before move: {error}", error=e
|
130
|
+
)
|
131
|
+
return backup_path, None
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import os
|
2
|
+
import webbrowser
|
3
|
+
from janito.plugins.tools.decorators import register_core_tool
|
4
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
5
|
+
from janito.report_events import ReportAction
|
6
|
+
from janito.i18n import tr
|
7
|
+
from janito.tools.loop_protection_decorator import protect_against_loops
|
8
|
+
|
9
|
+
|
10
|
+
@register_core_tool
|
11
|
+
class OpenHtmlInBrowser(ToolBase):
|
12
|
+
"""
|
13
|
+
Open the supplied HTML file in the default web browser.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
path (str): Path to the HTML file to open.
|
17
|
+
Returns:
|
18
|
+
str: Status message indicating the result.
|
19
|
+
"""
|
20
|
+
|
21
|
+
permissions = ToolPermissions(read=True)
|
22
|
+
tool_name = "open_html_in_browser"
|
23
|
+
|
24
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
25
|
+
def run(self, path: str) -> str:
|
26
|
+
if not path.strip():
|
27
|
+
self.report_warning(tr("ℹ️ Empty file path provided."))
|
28
|
+
return tr("Warning: Empty file path provided. Operation skipped.")
|
29
|
+
if not os.path.isfile(path):
|
30
|
+
self.report_error(tr("❗ File does not exist: {path}", path=path))
|
31
|
+
return tr("Warning: File does not exist: {path}", path=path)
|
32
|
+
if not path.lower().endswith((".html", ".htm")):
|
33
|
+
self.report_warning(tr("⚠️ Not an HTML file: {path}", path=path))
|
34
|
+
return tr("Warning: Not an HTML file: {path}", path=path)
|
35
|
+
url = "file://" + os.path.abspath(path)
|
36
|
+
self.report_action(
|
37
|
+
tr("📖 Opening HTML file in browser: {path}", path=path), ReportAction.READ
|
38
|
+
)
|
39
|
+
try:
|
40
|
+
webbrowser.open(url)
|
41
|
+
except Exception as err:
|
42
|
+
self.report_error(
|
43
|
+
tr("❗ Error opening HTML file: {path}: {err}", path=path, err=str(err))
|
44
|
+
)
|
45
|
+
return tr(
|
46
|
+
"Warning: Error opening HTML file: {path}: {err}",
|
47
|
+
path=path,
|
48
|
+
err=str(err),
|
49
|
+
)
|
50
|
+
self.report_success(tr("✅ HTML file opened in browser: {path}", path=path))
|
51
|
+
return tr("HTML file opened in browser: {path}", path=path)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import webbrowser
|
2
|
+
from janito.plugins.tools.decorators import register_core_tool
|
3
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
4
|
+
from janito.report_events import ReportAction
|
5
|
+
from janito.i18n import tr
|
6
|
+
from janito.tools.loop_protection_decorator import protect_against_loops
|
7
|
+
|
8
|
+
|
9
|
+
@register_core_tool
|
10
|
+
class OpenUrl(ToolBase):
|
11
|
+
"""
|
12
|
+
Open the supplied URL or local file in the default web browser.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
url (str): The URL or local file path (as a file:// URL) to open. Supports both web URLs (http, https) and local files (file://).
|
16
|
+
Returns:
|
17
|
+
str: Status message indicating the result.
|
18
|
+
"""
|
19
|
+
|
20
|
+
permissions = ToolPermissions(read=True)
|
21
|
+
tool_name = "open_url"
|
22
|
+
|
23
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="url")
|
24
|
+
def run(self, url: str) -> str:
|
25
|
+
if not url.strip():
|
26
|
+
self.report_warning(tr("ℹ️ Empty URL provided."))
|
27
|
+
return tr("Warning: Empty URL provided. Operation skipped.")
|
28
|
+
self.report_action(tr("🌐 Opening URL '{url}' ...", url=url), ReportAction.READ)
|
29
|
+
try:
|
30
|
+
webbrowser.open(url)
|
31
|
+
except Exception as err:
|
32
|
+
self.report_error(
|
33
|
+
tr("❗ Error opening URL: {url}: {err}", url=url, err=str(err))
|
34
|
+
)
|
35
|
+
return tr("Warning: Error opening URL: {url}: {err}", url=url, err=str(err))
|
36
|
+
self.report_success(tr("✅ URL opened in browser: {url}", url=url))
|
37
|
+
return tr("URL opened in browser: {url}", url=url)
|
@@ -5,12 +5,12 @@ import tempfile
|
|
5
5
|
import threading
|
6
6
|
from janito.tools.tool_base import ToolBase, ToolPermissions
|
7
7
|
from janito.report_events import ReportAction
|
8
|
-
from janito.tools.
|
8
|
+
from janito.plugins.tools.decorators import register_core_tool
|
9
9
|
from janito.i18n import tr
|
10
10
|
|
11
11
|
|
12
|
-
@
|
13
|
-
class
|
12
|
+
@register_core_tool
|
13
|
+
class PythonCodeRun(ToolBase):
|
14
14
|
"""
|
15
15
|
Tool to execute Python code by passing it to the interpreter via standard input (stdin).
|
16
16
|
|
@@ -93,6 +93,9 @@ class PythonCodeRunTool(ToolBase):
|
|
93
93
|
def stream_output(stream, file_obj, report_func, count_func):
|
94
94
|
nonlocal stdout_lines, stderr_lines
|
95
95
|
for line in stream:
|
96
|
+
# Check for cancellation
|
97
|
+
if hasattr(self, '_cancel_event') and self._cancel_event.is_set():
|
98
|
+
break
|
96
99
|
file_obj.write(line)
|
97
100
|
file_obj.flush()
|
98
101
|
report_func(line.rstrip("\r\n"))
|
@@ -101,6 +104,11 @@ class PythonCodeRunTool(ToolBase):
|
|
101
104
|
else:
|
102
105
|
stderr_lines += 1
|
103
106
|
|
107
|
+
# Set up cancellation event
|
108
|
+
from janito.llm.cancellation_manager import get_cancellation_manager
|
109
|
+
cancel_manager = get_cancellation_manager()
|
110
|
+
self._cancel_event = cancel_manager.get_current_cancel_event()
|
111
|
+
|
104
112
|
stdout_thread = threading.Thread(
|
105
113
|
target=stream_output,
|
106
114
|
args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
|
@@ -111,10 +119,18 @@ class PythonCodeRunTool(ToolBase):
|
|
111
119
|
)
|
112
120
|
stdout_thread.start()
|
113
121
|
stderr_thread.start()
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
122
|
+
|
123
|
+
try:
|
124
|
+
process.stdin.write(code)
|
125
|
+
process.stdin.close()
|
126
|
+
stdout_thread.join()
|
127
|
+
stderr_thread.join()
|
128
|
+
except Exception as e:
|
129
|
+
# Handle cancellation or other errors
|
130
|
+
if self._cancel_event and self._cancel_event.is_set():
|
131
|
+
process.kill()
|
132
|
+
self.report_warning(tr("Code execution cancelled by user"), ReportAction.EXECUTE)
|
133
|
+
raise
|
118
134
|
return stdout_lines, stderr_lines
|
119
135
|
|
120
136
|
def _wait_for_process(self, process, timeout):
|
@@ -5,12 +5,12 @@ import tempfile
|
|
5
5
|
import threading
|
6
6
|
from janito.tools.tool_base import ToolBase, ToolPermissions
|
7
7
|
from janito.report_events import ReportAction
|
8
|
-
from janito.tools.
|
8
|
+
from janito.plugins.tools.decorators import register_core_tool
|
9
9
|
from janito.i18n import tr
|
10
10
|
|
11
11
|
|
12
|
-
@
|
13
|
-
class
|
12
|
+
@register_core_tool
|
13
|
+
class PythonCommandRun(ToolBase):
|
14
14
|
"""
|
15
15
|
Tool to execute Python code using the `python -c` command-line flag.
|
16
16
|
|
@@ -92,6 +92,9 @@ class PythonCommandRunTool(ToolBase):
|
|
92
92
|
def stream_output(stream, file_obj, report_func, count_func):
|
93
93
|
nonlocal stdout_lines, stderr_lines
|
94
94
|
for line in stream:
|
95
|
+
# Check for cancellation
|
96
|
+
if hasattr(self, '_cancel_event') and self._cancel_event.is_set():
|
97
|
+
break
|
95
98
|
file_obj.write(line)
|
96
99
|
file_obj.flush()
|
97
100
|
from janito.tools.tool_base import ReportAction
|
@@ -102,6 +105,11 @@ class PythonCommandRunTool(ToolBase):
|
|
102
105
|
else:
|
103
106
|
stderr_lines += 1
|
104
107
|
|
108
|
+
# Set up cancellation event
|
109
|
+
from janito.llm.cancellation_manager import get_cancellation_manager
|
110
|
+
cancel_manager = get_cancellation_manager()
|
111
|
+
self._cancel_event = cancel_manager.get_current_cancel_event()
|
112
|
+
|
105
113
|
stdout_thread = threading.Thread(
|
106
114
|
target=stream_output,
|
107
115
|
args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
|
@@ -112,8 +120,16 @@ class PythonCommandRunTool(ToolBase):
|
|
112
120
|
)
|
113
121
|
stdout_thread.start()
|
114
122
|
stderr_thread.start()
|
115
|
-
|
116
|
-
|
123
|
+
|
124
|
+
try:
|
125
|
+
stdout_thread.join()
|
126
|
+
stderr_thread.join()
|
127
|
+
except Exception as e:
|
128
|
+
# Handle cancellation
|
129
|
+
if self._cancel_event and self._cancel_event.is_set():
|
130
|
+
process.kill()
|
131
|
+
self.report_warning(tr("Code execution cancelled by user"), ReportAction.EXECUTE)
|
132
|
+
raise
|
117
133
|
return stdout_lines, stderr_lines
|
118
134
|
|
119
135
|
def _wait_for_process(self, process, timeout):
|