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.
Files changed (159) hide show
  1. janito/README.md +3 -0
  2. janito/cli/chat_mode/bindings.py +50 -0
  3. janito/cli/chat_mode/session.py +12 -1
  4. janito/cli/chat_mode/shell/commands/multi.py +5 -0
  5. janito/cli/chat_mode/shell/commands/security/allowed_sites.py +47 -33
  6. janito/cli/cli_commands/check_tools.py +212 -0
  7. janito/cli/cli_commands/list_plugins.py +52 -43
  8. janito/cli/core/getters.py +3 -0
  9. janito/cli/core/model_guesser.py +40 -24
  10. janito/cli/main_cli.py +9 -12
  11. janito/cli/prompt_core.py +47 -9
  12. janito/cli/rich_terminal_reporter.py +2 -2
  13. janito/drivers/openai/driver.py +1 -0
  14. janito/drivers/zai/driver.py +1 -0
  15. janito/i18n/it.py +46 -46
  16. janito/llm/agent.py +32 -16
  17. janito/llm/auth_utils.py +14 -5
  18. janito/llm/cancellation_manager.py +63 -0
  19. janito/llm/driver.py +8 -0
  20. janito/llm/enter_cancellation.py +107 -0
  21. janito/plugin_system/__init__.py +10 -0
  22. janito/{plugins → plugin_system}/base.py +5 -2
  23. janito/plugin_system/core_loader.py +217 -0
  24. janito/plugin_system/core_loader_fixed.py +225 -0
  25. janito/plugins/__init__.py +31 -12
  26. janito/plugins/auto_loader.py +12 -11
  27. janito/plugins/auto_loader_fixed.py +12 -11
  28. janito/plugins/builtin.py +15 -1
  29. janito/plugins/core/__init__.py +7 -0
  30. janito/plugins/core/codeanalyzer/__init__.py +43 -0
  31. janito/plugins/core/filemanager/__init__.py +124 -0
  32. janito/plugins/core/filemanager/tools/create_file.py +87 -0
  33. janito/plugins/core/filemanager/tools/replace_text_in_file.py +270 -0
  34. janito/plugins/core/imagedisplay/__init__.py +14 -0
  35. janito/plugins/core/imagedisplay/plugin.py +51 -0
  36. janito/plugins/core/imagedisplay/tools/__init__.py +1 -0
  37. janito/plugins/core/imagedisplay/tools/show_image.py +83 -0
  38. janito/{tools/adapters/local → plugins/core/imagedisplay/tools}/show_image_grid.py +13 -5
  39. janito/plugins/core/system/__init__.py +23 -0
  40. janito/plugins/core/system/tools/run_bash_command.py +204 -0
  41. janito/plugins/core/system/tools/run_powershell_command.py +234 -0
  42. janito/plugins/core_adapter.py +89 -11
  43. janito/plugins/dev/__init__.py +7 -0
  44. janito/plugins/dev/pythondev/__init__.py +37 -0
  45. janito/plugins/dev/visualization/__init__.py +23 -0
  46. janito/plugins/discovery.py +5 -5
  47. janito/plugins/discovery_core.py +14 -9
  48. janito/plugins/example_plugin.py +108 -0
  49. janito/plugins/manager.py +1 -1
  50. janito/plugins/tools/__init__.py +10 -0
  51. janito/{tools/adapters/local → plugins/tools}/ask_user.py +3 -3
  52. janito/plugins/tools/copy_file.py +87 -0
  53. janito/plugins/tools/core_tools_plugin.py +87 -0
  54. janito/plugins/tools/create_directory.py +70 -0
  55. janito/{tools/adapters/local → plugins/tools}/create_file.py +6 -6
  56. janito/plugins/tools/decorators.py +19 -0
  57. janito/plugins/tools/delete_text_in_file.py +134 -0
  58. janito/{tools/adapters/local → plugins/tools}/fetch_url.py +3 -3
  59. janito/plugins/tools/find_files.py +143 -0
  60. janito/plugins/tools/get_file_outline/__init__.py +7 -0
  61. janito/plugins/tools/get_file_outline/core.py +122 -0
  62. janito/plugins/tools/get_file_outline/java_outline.py +47 -0
  63. janito/plugins/tools/get_file_outline/markdown_outline.py +14 -0
  64. janito/plugins/tools/get_file_outline/python_outline.py +303 -0
  65. janito/plugins/tools/get_file_outline/search_outline.py +36 -0
  66. janito/plugins/tools/move_file.py +131 -0
  67. janito/plugins/tools/open_html_in_browser.py +51 -0
  68. janito/plugins/tools/open_url.py +37 -0
  69. janito/{tools/adapters/local → plugins/tools}/python_code_run.py +23 -7
  70. janito/{tools/adapters/local → plugins/tools}/python_command_run.py +21 -5
  71. janito/{tools/adapters/local → plugins/tools}/python_file_run.py +21 -5
  72. janito/plugins/tools/read_chart.py +259 -0
  73. janito/plugins/tools/read_files.py +58 -0
  74. janito/plugins/tools/remove_directory.py +55 -0
  75. janito/plugins/tools/remove_file.py +58 -0
  76. janito/{tools/adapters/local → plugins/tools}/replace_text_in_file.py +4 -4
  77. janito/{tools/adapters/local → plugins/tools}/run_bash_command.py +3 -3
  78. janito/{tools/adapters/local → plugins/tools}/run_powershell_command.py +3 -3
  79. janito/plugins/tools/search_text/__init__.py +7 -0
  80. janito/plugins/tools/search_text/core.py +205 -0
  81. janito/plugins/tools/search_text/match_lines.py +67 -0
  82. janito/plugins/tools/search_text/pattern_utils.py +73 -0
  83. janito/plugins/tools/search_text/traverse_directory.py +145 -0
  84. janito/{tools/adapters/local → plugins/tools}/show_image.py +15 -6
  85. janito/plugins/tools/show_image_grid.py +85 -0
  86. janito/plugins/tools/validate_file_syntax/__init__.py +7 -0
  87. janito/plugins/tools/validate_file_syntax/core.py +114 -0
  88. janito/plugins/tools/validate_file_syntax/css_validator.py +35 -0
  89. janito/plugins/tools/validate_file_syntax/html_validator.py +100 -0
  90. janito/plugins/tools/validate_file_syntax/jinja2_validator.py +50 -0
  91. janito/plugins/tools/validate_file_syntax/js_validator.py +27 -0
  92. janito/plugins/tools/validate_file_syntax/json_validator.py +6 -0
  93. janito/plugins/tools/validate_file_syntax/markdown_validator.py +109 -0
  94. janito/plugins/tools/validate_file_syntax/ps1_validator.py +32 -0
  95. janito/plugins/tools/validate_file_syntax/python_validator.py +5 -0
  96. janito/plugins/tools/validate_file_syntax/xml_validator.py +11 -0
  97. janito/plugins/tools/validate_file_syntax/yaml_validator.py +6 -0
  98. janito/plugins/tools/view_file.py +172 -0
  99. janito/plugins/ui/__init__.py +7 -0
  100. janito/plugins/ui/userinterface/__init__.py +16 -0
  101. janito/plugins/ui/userinterface/tools/ask_user.py +110 -0
  102. janito/plugins/web/__init__.py +7 -0
  103. janito/plugins/web/webtools/__init__.py +33 -0
  104. janito/plugins/web/webtools/tools/fetch_url.py +458 -0
  105. janito/providers/__init__.py +1 -0
  106. janito/providers/together/__init__.py +1 -0
  107. janito/providers/together/model_info.py +69 -0
  108. janito/providers/together/provider.py +108 -0
  109. janito/tools/__init__.py +31 -7
  110. janito/tools/adapters/__init__.py +6 -1
  111. janito/tools/adapters/local/__init__.py +7 -70
  112. janito/tools/cli_initializer.py +88 -0
  113. janito/tools/initialize.py +70 -0
  114. janito/tools/loop_protection_decorator.py +114 -117
  115. janito-3.5.1.dist-info/METADATA +229 -0
  116. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/RECORD +155 -86
  117. janito/plugins/core_loader.py +0 -120
  118. janito/plugins/core_loader_fixed.py +0 -125
  119. janito/tools/function_adapter.py +0 -65
  120. janito-3.4.0.dist-info/METADATA +0 -84
  121. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/__init__.py +0 -0
  122. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/core.py +0 -0
  123. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/java_outline.py +0 -0
  124. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/markdown_outline.py +0 -0
  125. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/python_outline.py +0 -0
  126. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/search_outline.py +0 -0
  127. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/__init__.py +0 -0
  128. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/core.py +0 -0
  129. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/match_lines.py +0 -0
  130. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/pattern_utils.py +0 -0
  131. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/traverse_directory.py +0 -0
  132. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/copy_file.py +0 -0
  133. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/create_directory.py +0 -0
  134. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/delete_text_in_file.py +0 -0
  135. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/find_files.py +0 -0
  136. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/move_file.py +0 -0
  137. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/read_files.py +0 -0
  138. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_directory.py +0 -0
  139. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_file.py +0 -0
  140. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/__init__.py +0 -0
  141. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/core.py +0 -0
  142. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/css_validator.py +0 -0
  143. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/html_validator.py +0 -0
  144. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/jinja2_validator.py +0 -0
  145. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/js_validator.py +0 -0
  146. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/json_validator.py +0 -0
  147. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/markdown_validator.py +0 -0
  148. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/ps1_validator.py +0 -0
  149. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/python_validator.py +0 -0
  150. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/xml_validator.py +0 -0
  151. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/yaml_validator.py +0 -0
  152. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/view_file.py +0 -0
  153. /janito/{tools/adapters/local → plugins/dev/visualization/tools}/read_chart.py +0 -0
  154. /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_html_in_browser.py +0 -0
  155. /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_url.py +0 -0
  156. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/WHEEL +0 -0
  157. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/entry_points.txt +0 -0
  158. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/licenses/LICENSE +0 -0
  159. {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.adapters.local.adapter import register_local_tool
8
+ from janito.plugins.tools.decorators import register_core_tool
9
9
  from janito.i18n import tr
10
10
 
11
11
 
12
- @register_local_tool
13
- class PythonCodeRunTool(ToolBase):
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
- process.stdin.write(code)
115
- process.stdin.close()
116
- stdout_thread.join()
117
- stderr_thread.join()
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.adapters.local.adapter import register_local_tool
8
+ from janito.plugins.tools.decorators import register_core_tool
9
9
  from janito.i18n import tr
10
10
 
11
11
 
12
- @register_local_tool
13
- class PythonCommandRunTool(ToolBase):
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
- stdout_thread.join()
116
- stderr_thread.join()
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):