janito 2.7.0__py3-none-any.whl → 2.9.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 +0 -1
- janito/__main__.py +0 -1
- janito/_version.py +0 -3
- janito/agent/setup_agent.py +77 -10
- janito/agent/templates/profiles/{system_prompt_template_plain_software_developer.txt.j2 → system_prompt_template_Developer_with_Python_Tools.txt.j2} +5 -1
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +3 -12
- janito/cli/__init__.py +0 -1
- janito/cli/chat_mode/bindings.py +1 -1
- janito/cli/chat_mode/chat_entry.py +0 -2
- janito/cli/chat_mode/prompt_style.py +0 -3
- janito/cli/chat_mode/script_runner.py +9 -5
- janito/cli/chat_mode/session.py +100 -37
- janito/cli/chat_mode/session_profile_select.py +61 -52
- janito/cli/chat_mode/shell/commands/__init__.py +1 -5
- janito/cli/chat_mode/shell/commands/_priv_check.py +1 -0
- janito/cli/chat_mode/shell/commands/_priv_status.py +13 -0
- janito/cli/chat_mode/shell/commands/bang.py +10 -3
- janito/cli/chat_mode/shell/commands/conversation_restart.py +24 -7
- janito/cli/chat_mode/shell/commands/execute.py +22 -7
- janito/cli/chat_mode/shell/commands/help.py +4 -1
- janito/cli/chat_mode/shell/commands/model.py +13 -5
- janito/cli/chat_mode/shell/commands/privileges.py +21 -0
- janito/cli/chat_mode/shell/commands/prompt.py +0 -2
- janito/cli/chat_mode/shell/commands/read.py +22 -5
- janito/cli/chat_mode/shell/commands/tools.py +15 -4
- janito/cli/chat_mode/shell/commands/write.py +22 -5
- janito/cli/chat_mode/shell/input_history.py +3 -1
- janito/cli/chat_mode/shell/session/manager.py +0 -2
- janito/cli/chat_mode/toolbar.py +25 -19
- janito/cli/cli_commands/list_config.py +31 -0
- janito/cli/cli_commands/list_models.py +1 -1
- janito/cli/cli_commands/list_profiles.py +79 -0
- janito/cli/cli_commands/list_providers.py +1 -0
- janito/cli/cli_commands/list_tools.py +35 -7
- janito/cli/cli_commands/model_utils.py +5 -3
- janito/cli/cli_commands/show_config.py +16 -11
- janito/cli/cli_commands/show_system_prompt.py +23 -9
- janito/cli/config.py +0 -13
- janito/cli/core/getters.py +16 -1
- janito/cli/core/runner.py +25 -8
- janito/cli/core/setters.py +13 -76
- janito/cli/main_cli.py +60 -27
- janito/cli/prompt_core.py +19 -18
- janito/cli/prompt_setup.py +6 -3
- janito/cli/rich_terminal_reporter.py +19 -5
- janito/cli/single_shot_mode/handler.py +14 -5
- janito/cli/verbose_output.py +5 -1
- janito/config.py +1 -0
- janito/config_manager.py +15 -2
- janito/drivers/azure_openai/driver.py +27 -30
- janito/drivers/openai/driver.py +53 -36
- janito/formatting_token.py +12 -4
- janito/llm/agent.py +15 -6
- janito/llm/driver.py +1 -0
- janito/llm/provider.py +1 -1
- janito/provider_registry.py +31 -70
- janito/providers/__init__.py +1 -0
- janito/providers/anthropic/model_info.py +0 -1
- janito/providers/anthropic/provider.py +9 -14
- janito/providers/azure_openai/provider.py +10 -5
- janito/providers/deepseek/provider.py +5 -4
- janito/providers/google/model_info.py +4 -2
- janito/providers/google/provider.py +11 -5
- janito/providers/groq/__init__.py +1 -0
- janito/providers/groq/model_info.py +45 -0
- janito/providers/groq/provider.py +76 -0
- janito/providers/moonshotai/provider.py +11 -4
- janito/providers/openai/model_info.py +0 -1
- janito/providers/openai/provider.py +6 -7
- janito/tools/__init__.py +2 -0
- janito/tools/adapters/local/__init__.py +2 -1
- janito/tools/adapters/local/adapter.py +21 -4
- janito/tools/adapters/local/ask_user.py +1 -0
- janito/tools/adapters/local/copy_file.py +1 -0
- janito/tools/adapters/local/create_directory.py +1 -0
- janito/tools/adapters/local/create_file.py +1 -0
- janito/tools/adapters/local/delete_text_in_file.py +2 -1
- janito/tools/adapters/local/fetch_url.py +1 -0
- janito/tools/adapters/local/find_files.py +7 -6
- janito/tools/adapters/local/get_file_outline/core.py +1 -0
- janito/tools/adapters/local/get_file_outline/java_outline.py +22 -15
- janito/tools/adapters/local/get_file_outline/search_outline.py +1 -0
- janito/tools/adapters/local/move_file.py +1 -0
- janito/tools/adapters/local/open_html_in_browser.py +15 -5
- janito/tools/adapters/local/open_url.py +1 -0
- janito/tools/adapters/local/python_code_run.py +1 -0
- janito/tools/adapters/local/python_command_run.py +1 -0
- janito/tools/adapters/local/python_file_run.py +1 -0
- janito/tools/adapters/local/read_files.py +19 -4
- janito/tools/adapters/local/remove_directory.py +1 -0
- janito/tools/adapters/local/remove_file.py +1 -0
- janito/tools/adapters/local/replace_text_in_file.py +4 -3
- janito/tools/adapters/local/run_bash_command.py +1 -0
- janito/tools/adapters/local/run_powershell_command.py +1 -0
- janito/tools/adapters/local/search_text/core.py +18 -17
- janito/tools/adapters/local/search_text/match_lines.py +5 -5
- janito/tools/adapters/local/search_text/pattern_utils.py +1 -1
- janito/tools/adapters/local/search_text/traverse_directory.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/core.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +8 -1
- janito/tools/disabled_tools.py +68 -0
- janito/tools/path_security.py +18 -11
- janito/tools/permissions.py +6 -0
- janito/tools/permissions_parse.py +4 -3
- janito/tools/tool_base.py +11 -5
- janito/tools/tool_use_tracker.py +1 -4
- janito/tools/tool_utils.py +1 -1
- janito/tools/tools_adapter.py +57 -25
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/METADATA +11 -19
- janito-2.9.0.dist-info/RECORD +205 -0
- janito/cli/chat_mode/shell/commands/livelogs.py +0 -49
- janito/drivers/mistralai/driver.py +0 -41
- janito/providers/mistralai/model_info.py +0 -37
- janito/providers/mistralai/provider.py +0 -72
- janito/providers/provider_static_info.py +0 -21
- janito-2.7.0.dist-info/RECORD +0 -202
- /janito/agent/templates/profiles/{system_prompt_template_assistant.txt.j2 → system_prompt_template_model_conversation_without_tools_or_context.txt.j2} +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/WHEEL +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/entry_points.txt +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ from janito.tools.tool_base import ToolBase, ToolPermissions
|
|
5
5
|
from janito.report_events import ReportAction
|
6
6
|
from janito.i18n import tr
|
7
7
|
|
8
|
+
|
8
9
|
@register_local_tool
|
9
10
|
class OpenHtmlInBrowserTool(ToolBase):
|
10
11
|
"""
|
@@ -15,6 +16,7 @@ class OpenHtmlInBrowserTool(ToolBase):
|
|
15
16
|
Returns:
|
16
17
|
str: Status message indicating the result.
|
17
18
|
"""
|
19
|
+
|
18
20
|
permissions = ToolPermissions(read=True)
|
19
21
|
tool_name = "open_html_in_browser"
|
20
22
|
|
@@ -25,15 +27,23 @@ class OpenHtmlInBrowserTool(ToolBase):
|
|
25
27
|
if not os.path.isfile(path):
|
26
28
|
self.report_error(tr("❗ File does not exist: {path}", path=path))
|
27
29
|
return tr("Warning: File does not exist: {path}", path=path)
|
28
|
-
if not path.lower().endswith((
|
30
|
+
if not path.lower().endswith((".html", ".htm")):
|
29
31
|
self.report_warning(tr("⚠️ Not an HTML file: {path}", path=path))
|
30
32
|
return tr("Warning: Not an HTML file: {path}", path=path)
|
31
|
-
url =
|
32
|
-
self.report_action(
|
33
|
+
url = "file://" + os.path.abspath(path)
|
34
|
+
self.report_action(
|
35
|
+
tr("📖 Opening HTML file in browser: {path}", path=path), ReportAction.READ
|
36
|
+
)
|
33
37
|
try:
|
34
38
|
webbrowser.open(url)
|
35
39
|
except Exception as err:
|
36
|
-
self.report_error(
|
37
|
-
|
40
|
+
self.report_error(
|
41
|
+
tr("❗ Error opening HTML file: {path}: {err}", path=path, err=str(err))
|
42
|
+
)
|
43
|
+
return tr(
|
44
|
+
"Warning: Error opening HTML file: {path}: {err}",
|
45
|
+
path=path,
|
46
|
+
err=str(err),
|
47
|
+
)
|
38
48
|
self.report_success(tr("✅ HTML file opened in browser: {path}", path=path))
|
39
49
|
return tr("HTML file opened in browser: {path}", path=path)
|
@@ -4,6 +4,7 @@ from janito.tools.adapters.local.adapter import register_local_tool
|
|
4
4
|
from janito.tools.tool_utils import pluralize
|
5
5
|
from janito.i18n import tr
|
6
6
|
|
7
|
+
|
7
8
|
@register_local_tool
|
8
9
|
class ReadFilesTool(ToolBase):
|
9
10
|
"""
|
@@ -15,18 +16,24 @@ class ReadFilesTool(ToolBase):
|
|
15
16
|
Returns:
|
16
17
|
str: Concatenated content of all files, each prefixed by a header with the file name. If a file cannot be read, an error message is included for that file.
|
17
18
|
"""
|
19
|
+
|
18
20
|
permissions = ToolPermissions(read=True)
|
19
21
|
tool_name = "read_files"
|
20
22
|
|
21
23
|
def run(self, paths: list[str]) -> str:
|
22
24
|
from janito.tools.tool_utils import display_path
|
23
25
|
import os
|
26
|
+
|
24
27
|
results = []
|
25
28
|
for path in paths:
|
26
29
|
disp_path = display_path(path)
|
27
|
-
self.report_action(
|
30
|
+
self.report_action(
|
31
|
+
tr("📖 Read '{disp_path}'", disp_path=disp_path), ReportAction.READ
|
32
|
+
)
|
28
33
|
if not os.path.isfile(path):
|
29
|
-
self.report_warning(
|
34
|
+
self.report_warning(
|
35
|
+
tr("❗ not found: {disp_path}", disp_path=disp_path)
|
36
|
+
)
|
30
37
|
results.append(f"--- File: {disp_path} (not found) ---\n")
|
31
38
|
continue
|
32
39
|
try:
|
@@ -35,6 +42,14 @@ class ReadFilesTool(ToolBase):
|
|
35
42
|
results.append(f"--- File: {disp_path} ---\n{content}\n")
|
36
43
|
self.report_success(tr("✅ Read {disp_path}", disp_path=disp_path))
|
37
44
|
except Exception as e:
|
38
|
-
self.report_error(
|
39
|
-
|
45
|
+
self.report_error(
|
46
|
+
tr(
|
47
|
+
" ❌ Error reading {disp_path}: {error}",
|
48
|
+
disp_path=disp_path,
|
49
|
+
error=e,
|
50
|
+
)
|
51
|
+
)
|
52
|
+
results.append(
|
53
|
+
f"--- File: {disp_path} (error) ---\nError reading file: {e}\n"
|
54
|
+
)
|
40
55
|
return "\n".join(results)
|
@@ -28,6 +28,7 @@ class ReplaceTextInFileTool(ToolBase):
|
|
28
28
|
- "No changes made. [Warning: Search text not found in file] Please review the original file."
|
29
29
|
- "Error replacing text: <error message>"
|
30
30
|
"""
|
31
|
+
|
31
32
|
permissions = ToolPermissions(read=True, write=True)
|
32
33
|
tool_name = "replace_text_in_file"
|
33
34
|
|
@@ -85,9 +86,9 @@ class ReplaceTextInFileTool(ToolBase):
|
|
85
86
|
line_delta_str,
|
86
87
|
replace_all,
|
87
88
|
)
|
88
|
-
return self._format_final_msg(
|
89
|
-
|
90
|
-
)
|
89
|
+
return self._format_final_msg(path, warning, match_info, details) + (
|
90
|
+
f"\n{validation_result}" if validation_result else ""
|
91
|
+
)
|
91
92
|
except Exception as e:
|
92
93
|
self.report_error(tr(" ❌ Error"), ReportAction.REPLACE)
|
93
94
|
return tr("Error replacing text: {error}", error=e)
|
@@ -15,11 +15,11 @@ from janito.tools.adapters.local.adapter import register_local_tool as register_
|
|
15
15
|
@register_tool
|
16
16
|
class SearchTextTool(ToolBase):
|
17
17
|
"""
|
18
|
-
Search for a text
|
18
|
+
Search for a text query in all files within one or more directories or file paths and return matching lines or counts. Respects .gitignore.
|
19
19
|
Args:
|
20
20
|
paths (str): String of one or more paths (space-separated) to search in. Each path can be a directory or a file.
|
21
|
-
|
22
|
-
|
21
|
+
query (str): Text or regular expression to search for in files. Must not be empty. When use_regex=True, this is treated as a regex pattern; otherwise as plain text.
|
22
|
+
use_regex (bool): If True, treat query as a regular expression. If False, treat as plain text (default).
|
23
23
|
case_sensitive (bool): If False, perform a case-insensitive search. Default is True (case sensitive).
|
24
24
|
max_depth (int, optional): Maximum directory depth to search. If 0 (default), search is recursive with no depth limit. If >0, limits recursion to that depth. Setting max_depth=1 disables recursion (only top-level directory). Ignored for file paths.
|
25
25
|
max_results (int, optional): Maximum number of results to return. Defaults to 100. 0 means no limit.
|
@@ -29,13 +29,14 @@ class SearchTextTool(ToolBase):
|
|
29
29
|
If count_only is True, returns per-file and total match counts.
|
30
30
|
If max_results is reached, appends a note to the output.
|
31
31
|
"""
|
32
|
+
|
32
33
|
permissions = ToolPermissions(read=True)
|
33
34
|
tool_name = "search_text"
|
34
35
|
|
35
36
|
def _handle_file(
|
36
37
|
self,
|
37
38
|
search_path,
|
38
|
-
|
39
|
+
query,
|
39
40
|
regex,
|
40
41
|
use_regex,
|
41
42
|
case_sensitive,
|
@@ -46,7 +47,7 @@ class SearchTextTool(ToolBase):
|
|
46
47
|
if count_only:
|
47
48
|
match_count, dir_limit_reached, _ = read_file_lines(
|
48
49
|
search_path,
|
49
|
-
|
50
|
+
query,
|
50
51
|
regex,
|
51
52
|
use_regex,
|
52
53
|
case_sensitive,
|
@@ -59,7 +60,7 @@ class SearchTextTool(ToolBase):
|
|
59
60
|
else:
|
60
61
|
dir_output, dir_limit_reached, match_count_list = read_file_lines(
|
61
62
|
search_path,
|
62
|
-
|
63
|
+
query,
|
63
64
|
regex,
|
64
65
|
use_regex,
|
65
66
|
case_sensitive,
|
@@ -77,7 +78,7 @@ class SearchTextTool(ToolBase):
|
|
77
78
|
def _handle_path(
|
78
79
|
self,
|
79
80
|
search_path,
|
80
|
-
|
81
|
+
query,
|
81
82
|
regex,
|
82
83
|
use_regex,
|
83
84
|
case_sensitive,
|
@@ -87,9 +88,9 @@ class SearchTextTool(ToolBase):
|
|
87
88
|
count_only,
|
88
89
|
):
|
89
90
|
info_str = tr(
|
90
|
-
"🔍 Search {search_type} '{
|
91
|
+
"🔍 Search {search_type} '{query}' in '{disp_path}'",
|
91
92
|
search_type=("regex" if use_regex else "text"),
|
92
|
-
|
93
|
+
query=query,
|
93
94
|
disp_path=display_path(search_path),
|
94
95
|
)
|
95
96
|
if max_depth > 0:
|
@@ -100,7 +101,7 @@ class SearchTextTool(ToolBase):
|
|
100
101
|
if os.path.isfile(search_path):
|
101
102
|
dir_output, dir_limit_reached, per_file_counts = self._handle_file(
|
102
103
|
search_path,
|
103
|
-
|
104
|
+
query,
|
104
105
|
regex,
|
105
106
|
use_regex,
|
106
107
|
case_sensitive,
|
@@ -112,7 +113,7 @@ class SearchTextTool(ToolBase):
|
|
112
113
|
if count_only:
|
113
114
|
per_file_counts, dir_limit_reached, _ = traverse_directory(
|
114
115
|
search_path,
|
115
|
-
|
116
|
+
query,
|
116
117
|
regex,
|
117
118
|
use_regex,
|
118
119
|
case_sensitive,
|
@@ -125,7 +126,7 @@ class SearchTextTool(ToolBase):
|
|
125
126
|
else:
|
126
127
|
dir_output, dir_limit_reached, per_file_counts = traverse_directory(
|
127
128
|
search_path,
|
128
|
-
|
129
|
+
query,
|
129
130
|
regex,
|
130
131
|
use_regex,
|
131
132
|
case_sensitive,
|
@@ -154,15 +155,15 @@ class SearchTextTool(ToolBase):
|
|
154
155
|
def run(
|
155
156
|
self,
|
156
157
|
paths: str,
|
157
|
-
|
158
|
-
|
158
|
+
query: str,
|
159
|
+
use_regex: bool = False,
|
159
160
|
case_sensitive: bool = False,
|
160
161
|
max_depth: int = 0,
|
161
162
|
max_results: int = 100,
|
162
163
|
count_only: bool = False,
|
163
164
|
) -> str:
|
164
165
|
regex, use_regex, error_msg = prepare_pattern(
|
165
|
-
|
166
|
+
query, use_regex, case_sensitive, self.report_error, self.report_warning
|
166
167
|
)
|
167
168
|
if error_msg:
|
168
169
|
return error_msg
|
@@ -173,7 +174,7 @@ class SearchTextTool(ToolBase):
|
|
173
174
|
info_str, dir_output, dir_limit_reached, per_file_counts = (
|
174
175
|
self._handle_path(
|
175
176
|
search_path,
|
176
|
-
|
177
|
+
query,
|
177
178
|
regex,
|
178
179
|
use_regex,
|
179
180
|
case_sensitive,
|
@@ -186,7 +187,7 @@ class SearchTextTool(ToolBase):
|
|
186
187
|
if count_only:
|
187
188
|
all_per_file_counts.extend(per_file_counts)
|
188
189
|
result_str = format_result(
|
189
|
-
|
190
|
+
query,
|
190
191
|
use_regex,
|
191
192
|
dir_output,
|
192
193
|
dir_limit_reached,
|
@@ -20,12 +20,12 @@ def is_binary_file(path, blocksize=1024):
|
|
20
20
|
return False
|
21
21
|
|
22
22
|
|
23
|
-
def match_line(line,
|
23
|
+
def match_line(line, query, regex, use_regex, case_sensitive):
|
24
24
|
if use_regex:
|
25
25
|
return regex and regex.search(line)
|
26
26
|
if not case_sensitive:
|
27
|
-
return
|
28
|
-
return
|
27
|
+
return query.lower() in line.lower()
|
28
|
+
return query in line
|
29
29
|
|
30
30
|
|
31
31
|
def should_limit(max_results, total_results, match_count, count_only, dir_output):
|
@@ -37,7 +37,7 @@ def should_limit(max_results, total_results, match_count, count_only, dir_output
|
|
37
37
|
|
38
38
|
def read_file_lines(
|
39
39
|
path,
|
40
|
-
|
40
|
+
query,
|
41
41
|
regex,
|
42
42
|
use_regex,
|
43
43
|
case_sensitive,
|
@@ -53,7 +53,7 @@ def read_file_lines(
|
|
53
53
|
open_kwargs = {"mode": "r", "encoding": "utf-8"}
|
54
54
|
with open(path, **open_kwargs) as f:
|
55
55
|
for lineno, line in enumerate(f, 1):
|
56
|
-
if match_line(line,
|
56
|
+
if match_line(line, query, regex, use_regex, case_sensitive):
|
57
57
|
match_count += 1
|
58
58
|
if not count_only:
|
59
59
|
dir_output.append(f"{path}:{lineno}: {line.rstrip()}")
|
@@ -42,7 +42,7 @@ def prepare_pattern(pattern, is_regex, case_sensitive, report_error, report_warn
|
|
42
42
|
|
43
43
|
|
44
44
|
def format_result(
|
45
|
-
|
45
|
+
query, use_regex, output, limit_reached, count_only=False, per_file_counts=None
|
46
46
|
):
|
47
47
|
# Ensure output is always a list for joining
|
48
48
|
if output is None or not isinstance(output, (list, tuple)):
|
@@ -26,7 +26,7 @@ def filter_dirs(dirs, root, gitignore_filter):
|
|
26
26
|
def process_file_count_only(
|
27
27
|
path,
|
28
28
|
per_file_counts,
|
29
|
-
|
29
|
+
query,
|
30
30
|
regex,
|
31
31
|
use_regex,
|
32
32
|
case_sensitive,
|
@@ -35,7 +35,7 @@ def process_file_count_only(
|
|
35
35
|
):
|
36
36
|
match_count, file_limit_reached, _ = read_file_lines(
|
37
37
|
path,
|
38
|
-
|
38
|
+
query,
|
39
39
|
regex,
|
40
40
|
use_regex,
|
41
41
|
case_sensitive,
|
@@ -52,7 +52,7 @@ def process_file_collect(
|
|
52
52
|
path,
|
53
53
|
dir_output,
|
54
54
|
per_file_counts,
|
55
|
-
|
55
|
+
query,
|
56
56
|
regex,
|
57
57
|
use_regex,
|
58
58
|
case_sensitive,
|
@@ -61,7 +61,7 @@ def process_file_collect(
|
|
61
61
|
):
|
62
62
|
actual_match_count, file_limit_reached, file_lines_output = read_file_lines(
|
63
63
|
path,
|
64
|
-
|
64
|
+
query,
|
65
65
|
regex,
|
66
66
|
use_regex,
|
67
67
|
case_sensitive,
|
@@ -86,7 +86,7 @@ def should_limit_depth(root, search_path, max_depth, dirs):
|
|
86
86
|
|
87
87
|
def traverse_directory(
|
88
88
|
search_path,
|
89
|
-
|
89
|
+
query,
|
90
90
|
regex,
|
91
91
|
use_regex,
|
92
92
|
case_sensitive,
|
@@ -111,7 +111,7 @@ def traverse_directory(
|
|
111
111
|
file_limit_reached = process_file_count_only(
|
112
112
|
path,
|
113
113
|
per_file_counts,
|
114
|
-
|
114
|
+
query,
|
115
115
|
regex,
|
116
116
|
use_regex,
|
117
117
|
case_sensitive,
|
@@ -126,7 +126,7 @@ def traverse_directory(
|
|
126
126
|
path,
|
127
127
|
dir_output,
|
128
128
|
per_file_counts,
|
129
|
-
|
129
|
+
query,
|
130
130
|
regex,
|
131
131
|
use_regex,
|
132
132
|
case_sensitive,
|
@@ -82,6 +82,7 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
82
82
|
- "⚠️ Warning: Syntax error: <error message>"
|
83
83
|
- "⚠️ Warning: Unsupported file extension: <ext>"
|
84
84
|
"""
|
85
|
+
|
85
86
|
permissions = ToolPermissions(read=True)
|
86
87
|
tool_name = "validate_file_syntax"
|
87
88
|
|
@@ -96,7 +97,6 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
96
97
|
)
|
97
98
|
result = validate_file_syntax(
|
98
99
|
path,
|
99
|
-
|
100
100
|
report_warning=self.report_warning,
|
101
101
|
report_success=self.report_success,
|
102
102
|
)
|
@@ -1,6 +1,10 @@
|
|
1
1
|
from janito.i18n import tr
|
2
2
|
import re
|
3
|
-
|
3
|
+
|
4
|
+
try:
|
5
|
+
from lxml import etree
|
6
|
+
except ImportError:
|
7
|
+
etree = None
|
4
8
|
|
5
9
|
|
6
10
|
def validate_html(path: str) -> str:
|
@@ -48,6 +52,9 @@ def _find_js_outside_script(html_content):
|
|
48
52
|
|
49
53
|
def _parse_html_and_collect_errors(path):
|
50
54
|
lxml_error = None
|
55
|
+
if etree is None:
|
56
|
+
lxml_error = tr("⚠️ lxml not installed. Cannot validate HTML.")
|
57
|
+
return lxml_error
|
51
58
|
try:
|
52
59
|
parser = etree.HTMLParser(recover=False)
|
53
60
|
with open(path, "rb") as f:
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"""Management of disabled tools configuration."""
|
2
|
+
|
3
|
+
|
4
|
+
class DisabledToolsState:
|
5
|
+
"""Singleton to manage disabled tools configuration."""
|
6
|
+
|
7
|
+
_instance = None
|
8
|
+
_disabled_tools = set()
|
9
|
+
|
10
|
+
def __new__(cls):
|
11
|
+
if cls._instance is None:
|
12
|
+
cls._instance = super().__new__(cls)
|
13
|
+
return cls._instance
|
14
|
+
|
15
|
+
@classmethod
|
16
|
+
def get_disabled_tools(cls):
|
17
|
+
"""Get the set of disabled tool names."""
|
18
|
+
return cls._disabled_tools.copy()
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def set_disabled_tools(cls, tool_names):
|
22
|
+
"""Set the disabled tools from a list or set of tool names."""
|
23
|
+
if isinstance(tool_names, str):
|
24
|
+
tool_names = [
|
25
|
+
name.strip() for name in tool_names.split(",") if name.strip()
|
26
|
+
]
|
27
|
+
cls._disabled_tools = set(tool_names)
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def is_tool_disabled(cls, tool_name):
|
31
|
+
"""Check if a specific tool is disabled."""
|
32
|
+
return tool_name in cls._disabled_tools
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def disable_tool(cls, tool_name):
|
36
|
+
"""Add a tool to the disabled list."""
|
37
|
+
cls._disabled_tools.add(tool_name)
|
38
|
+
|
39
|
+
@classmethod
|
40
|
+
def enable_tool(cls, tool_name):
|
41
|
+
"""Remove a tool from the disabled list."""
|
42
|
+
cls._disabled_tools.discard(tool_name)
|
43
|
+
|
44
|
+
|
45
|
+
# Convenience functions
|
46
|
+
def get_disabled_tools():
|
47
|
+
"""Get the current set of disabled tools."""
|
48
|
+
return DisabledToolsState.get_disabled_tools()
|
49
|
+
|
50
|
+
|
51
|
+
def set_disabled_tools(tool_names):
|
52
|
+
"""Set the disabled tools from a list, set, or comma-separated string."""
|
53
|
+
DisabledToolsState.set_disabled_tools(tool_names)
|
54
|
+
|
55
|
+
|
56
|
+
def is_tool_disabled(tool_name):
|
57
|
+
"""Check if a specific tool is disabled."""
|
58
|
+
return DisabledToolsState.is_tool_disabled(tool_name)
|
59
|
+
|
60
|
+
|
61
|
+
def load_disabled_tools_from_config():
|
62
|
+
"""Load disabled tools from global config."""
|
63
|
+
from janito.config import config
|
64
|
+
|
65
|
+
disabled_str = config.get("disabled_tools", "")
|
66
|
+
if disabled_str:
|
67
|
+
DisabledToolsState.set_disabled_tools(disabled_str)
|
68
|
+
return DisabledToolsState.get_disabled_tools()
|
janito/tools/path_security.py
CHANGED
@@ -20,6 +20,7 @@ Public interface
|
|
20
20
|
Both helpers raise :class:`PathSecurityError` if a path tries to escape the
|
21
21
|
workspace.
|
22
22
|
"""
|
23
|
+
|
23
24
|
from __future__ import annotations
|
24
25
|
|
25
26
|
import os
|
@@ -46,7 +47,11 @@ class PathSecurityError(Exception):
|
|
46
47
|
# ---------------------------------------------------------------------------
|
47
48
|
|
48
49
|
|
49
|
-
def is_path_within_workdir(
|
50
|
+
def is_path_within_workdir(
|
51
|
+
path: str, workdir: str | None
|
52
|
+
) -> (
|
53
|
+
bool
|
54
|
+
): # noqa: D401 – we start with an imperative verb # noqa: D401 – we start with an imperative verb
|
50
55
|
"""Return *True* if *path* is located inside *workdir* (or equals it).
|
51
56
|
|
52
57
|
Relative *path*s are **resolved relative to the *workdir***, *not* to the
|
@@ -84,6 +89,7 @@ def is_path_within_workdir(path: str, workdir: str | None) -> bool: # noqa: D40
|
|
84
89
|
|
85
90
|
# Additionally allow files located inside the system temporary directory.
|
86
91
|
import tempfile
|
92
|
+
|
87
93
|
abs_tempdir = os.path.abspath(tempfile.gettempdir())
|
88
94
|
try:
|
89
95
|
common_temp = os.path.commonpath([abs_tempdir, abs_path])
|
@@ -129,20 +135,18 @@ def _extract_path_keys_from_schema(schema: Mapping[str, Any]) -> set[str]:
|
|
129
135
|
path_keys: set[str] = set()
|
130
136
|
if schema is not None:
|
131
137
|
for k, v in schema.get("properties", {}).items():
|
132
|
-
if (
|
133
|
-
v.get("
|
134
|
-
|
135
|
-
v.get("
|
136
|
-
|
137
|
-
|
138
|
-
or k.endswith("path")
|
139
|
-
or k == "path"
|
140
|
-
)
|
138
|
+
if v.get("format") == "path" or (
|
139
|
+
v.get("type") == "string"
|
140
|
+
and (
|
141
|
+
"path" in v.get("description", "").lower()
|
142
|
+
or k.endswith("path")
|
143
|
+
or k == "path"
|
141
144
|
)
|
142
145
|
):
|
143
146
|
path_keys.add(k)
|
144
147
|
return path_keys
|
145
148
|
|
149
|
+
|
146
150
|
def _validate_argument_value(key: str, value: Any, workdir: str) -> None:
|
147
151
|
"""Validate a single argument value (string or list of strings) for path security."""
|
148
152
|
# Single string argument → validate directly.
|
@@ -156,6 +160,7 @@ def _validate_argument_value(key: str, value: Any, workdir: str) -> None:
|
|
156
160
|
if not is_path_within_workdir(item, workdir):
|
157
161
|
_raise_outside_workspace_error(key, item, workdir)
|
158
162
|
|
163
|
+
|
159
164
|
def validate_paths_in_arguments(
|
160
165
|
arguments: Mapping[str, Any] | None,
|
161
166
|
workdir: str | None,
|
@@ -190,7 +195,9 @@ def validate_paths_in_arguments(
|
|
190
195
|
# ---------------------------------------------------------------------------
|
191
196
|
|
192
197
|
|
193
|
-
def _raise_outside_workspace_error(
|
198
|
+
def _raise_outside_workspace_error(
|
199
|
+
key: str, path: str, workdir: str
|
200
|
+
) -> None: # noqa: D401
|
194
201
|
"""Raise a consistent :class:`PathSecurityError` for *path*."""
|
195
202
|
abs_workdir = os.path.abspath(workdir)
|
196
203
|
attempted = (
|
janito/tools/permissions.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from janito.tools.tool_base import ToolPermissions
|
2
2
|
|
3
|
+
|
3
4
|
class AllowedPermissionsState:
|
4
5
|
_instance = None
|
5
6
|
_permissions = ToolPermissions(read=False, write=False, execute=False)
|
@@ -30,16 +31,21 @@ class AllowedPermissionsState:
|
|
30
31
|
def get_default_permissions(cls):
|
31
32
|
return cls._default_permissions
|
32
33
|
|
34
|
+
|
33
35
|
# Convenience functions
|
34
36
|
|
37
|
+
|
35
38
|
def get_global_allowed_permissions():
|
36
39
|
return AllowedPermissionsState.get_permissions()
|
37
40
|
|
41
|
+
|
38
42
|
def set_global_allowed_permissions(permissions):
|
39
43
|
AllowedPermissionsState.set_permissions(permissions)
|
40
44
|
|
45
|
+
|
41
46
|
def set_default_allowed_permissions(permissions):
|
42
47
|
AllowedPermissionsState.set_default_permissions(permissions)
|
43
48
|
|
49
|
+
|
44
50
|
def get_default_allowed_permissions():
|
45
51
|
return AllowedPermissionsState.get_default_permissions()
|
@@ -1,12 +1,13 @@
|
|
1
1
|
from janito.tools.tool_base import ToolPermissions
|
2
2
|
|
3
|
+
|
3
4
|
def parse_permissions_string(perm_str: str) -> ToolPermissions:
|
4
5
|
"""
|
5
6
|
Parse a string like 'rwx', 'rw', 'r', etc. into a ToolPermissions object.
|
6
7
|
"""
|
7
8
|
perm_str = perm_str.lower()
|
8
9
|
return ToolPermissions(
|
9
|
-
read=
|
10
|
-
write=
|
11
|
-
execute=
|
10
|
+
read="r" in perm_str,
|
11
|
+
write="w" in perm_str,
|
12
|
+
execute="x" in perm_str,
|
12
13
|
)
|