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.
Files changed (121) hide show
  1. janito/__init__.py +0 -1
  2. janito/__main__.py +0 -1
  3. janito/_version.py +0 -3
  4. janito/agent/setup_agent.py +77 -10
  5. janito/agent/templates/profiles/{system_prompt_template_plain_software_developer.txt.j2 → system_prompt_template_Developer_with_Python_Tools.txt.j2} +5 -1
  6. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +3 -12
  7. janito/cli/__init__.py +0 -1
  8. janito/cli/chat_mode/bindings.py +1 -1
  9. janito/cli/chat_mode/chat_entry.py +0 -2
  10. janito/cli/chat_mode/prompt_style.py +0 -3
  11. janito/cli/chat_mode/script_runner.py +9 -5
  12. janito/cli/chat_mode/session.py +100 -37
  13. janito/cli/chat_mode/session_profile_select.py +61 -52
  14. janito/cli/chat_mode/shell/commands/__init__.py +1 -5
  15. janito/cli/chat_mode/shell/commands/_priv_check.py +1 -0
  16. janito/cli/chat_mode/shell/commands/_priv_status.py +13 -0
  17. janito/cli/chat_mode/shell/commands/bang.py +10 -3
  18. janito/cli/chat_mode/shell/commands/conversation_restart.py +24 -7
  19. janito/cli/chat_mode/shell/commands/execute.py +22 -7
  20. janito/cli/chat_mode/shell/commands/help.py +4 -1
  21. janito/cli/chat_mode/shell/commands/model.py +13 -5
  22. janito/cli/chat_mode/shell/commands/privileges.py +21 -0
  23. janito/cli/chat_mode/shell/commands/prompt.py +0 -2
  24. janito/cli/chat_mode/shell/commands/read.py +22 -5
  25. janito/cli/chat_mode/shell/commands/tools.py +15 -4
  26. janito/cli/chat_mode/shell/commands/write.py +22 -5
  27. janito/cli/chat_mode/shell/input_history.py +3 -1
  28. janito/cli/chat_mode/shell/session/manager.py +0 -2
  29. janito/cli/chat_mode/toolbar.py +25 -19
  30. janito/cli/cli_commands/list_config.py +31 -0
  31. janito/cli/cli_commands/list_models.py +1 -1
  32. janito/cli/cli_commands/list_profiles.py +79 -0
  33. janito/cli/cli_commands/list_providers.py +1 -0
  34. janito/cli/cli_commands/list_tools.py +35 -7
  35. janito/cli/cli_commands/model_utils.py +5 -3
  36. janito/cli/cli_commands/show_config.py +16 -11
  37. janito/cli/cli_commands/show_system_prompt.py +23 -9
  38. janito/cli/config.py +0 -13
  39. janito/cli/core/getters.py +16 -1
  40. janito/cli/core/runner.py +25 -8
  41. janito/cli/core/setters.py +13 -76
  42. janito/cli/main_cli.py +60 -27
  43. janito/cli/prompt_core.py +19 -18
  44. janito/cli/prompt_setup.py +6 -3
  45. janito/cli/rich_terminal_reporter.py +19 -5
  46. janito/cli/single_shot_mode/handler.py +14 -5
  47. janito/cli/verbose_output.py +5 -1
  48. janito/config.py +1 -0
  49. janito/config_manager.py +15 -2
  50. janito/drivers/azure_openai/driver.py +27 -30
  51. janito/drivers/openai/driver.py +53 -36
  52. janito/formatting_token.py +12 -4
  53. janito/llm/agent.py +15 -6
  54. janito/llm/driver.py +1 -0
  55. janito/llm/provider.py +1 -1
  56. janito/provider_registry.py +31 -70
  57. janito/providers/__init__.py +1 -0
  58. janito/providers/anthropic/model_info.py +0 -1
  59. janito/providers/anthropic/provider.py +9 -14
  60. janito/providers/azure_openai/provider.py +10 -5
  61. janito/providers/deepseek/provider.py +5 -4
  62. janito/providers/google/model_info.py +4 -2
  63. janito/providers/google/provider.py +11 -5
  64. janito/providers/groq/__init__.py +1 -0
  65. janito/providers/groq/model_info.py +45 -0
  66. janito/providers/groq/provider.py +76 -0
  67. janito/providers/moonshotai/provider.py +11 -4
  68. janito/providers/openai/model_info.py +0 -1
  69. janito/providers/openai/provider.py +6 -7
  70. janito/tools/__init__.py +2 -0
  71. janito/tools/adapters/local/__init__.py +2 -1
  72. janito/tools/adapters/local/adapter.py +21 -4
  73. janito/tools/adapters/local/ask_user.py +1 -0
  74. janito/tools/adapters/local/copy_file.py +1 -0
  75. janito/tools/adapters/local/create_directory.py +1 -0
  76. janito/tools/adapters/local/create_file.py +1 -0
  77. janito/tools/adapters/local/delete_text_in_file.py +2 -1
  78. janito/tools/adapters/local/fetch_url.py +1 -0
  79. janito/tools/adapters/local/find_files.py +7 -6
  80. janito/tools/adapters/local/get_file_outline/core.py +1 -0
  81. janito/tools/adapters/local/get_file_outline/java_outline.py +22 -15
  82. janito/tools/adapters/local/get_file_outline/search_outline.py +1 -0
  83. janito/tools/adapters/local/move_file.py +1 -0
  84. janito/tools/adapters/local/open_html_in_browser.py +15 -5
  85. janito/tools/adapters/local/open_url.py +1 -0
  86. janito/tools/adapters/local/python_code_run.py +1 -0
  87. janito/tools/adapters/local/python_command_run.py +1 -0
  88. janito/tools/adapters/local/python_file_run.py +1 -0
  89. janito/tools/adapters/local/read_files.py +19 -4
  90. janito/tools/adapters/local/remove_directory.py +1 -0
  91. janito/tools/adapters/local/remove_file.py +1 -0
  92. janito/tools/adapters/local/replace_text_in_file.py +4 -3
  93. janito/tools/adapters/local/run_bash_command.py +1 -0
  94. janito/tools/adapters/local/run_powershell_command.py +1 -0
  95. janito/tools/adapters/local/search_text/core.py +18 -17
  96. janito/tools/adapters/local/search_text/match_lines.py +5 -5
  97. janito/tools/adapters/local/search_text/pattern_utils.py +1 -1
  98. janito/tools/adapters/local/search_text/traverse_directory.py +7 -7
  99. janito/tools/adapters/local/validate_file_syntax/core.py +1 -1
  100. janito/tools/adapters/local/validate_file_syntax/html_validator.py +8 -1
  101. janito/tools/disabled_tools.py +68 -0
  102. janito/tools/path_security.py +18 -11
  103. janito/tools/permissions.py +6 -0
  104. janito/tools/permissions_parse.py +4 -3
  105. janito/tools/tool_base.py +11 -5
  106. janito/tools/tool_use_tracker.py +1 -4
  107. janito/tools/tool_utils.py +1 -1
  108. janito/tools/tools_adapter.py +57 -25
  109. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/METADATA +11 -19
  110. janito-2.9.0.dist-info/RECORD +205 -0
  111. janito/cli/chat_mode/shell/commands/livelogs.py +0 -49
  112. janito/drivers/mistralai/driver.py +0 -41
  113. janito/providers/mistralai/model_info.py +0 -37
  114. janito/providers/mistralai/provider.py +0 -72
  115. janito/providers/provider_static_info.py +0 -21
  116. janito-2.7.0.dist-info/RECORD +0 -202
  117. /janito/agent/templates/profiles/{system_prompt_template_assistant.txt.j2 → system_prompt_template_model_conversation_without_tools_or_context.txt.j2} +0 -0
  118. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/WHEEL +0 -0
  119. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/entry_points.txt +0 -0
  120. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/licenses/LICENSE +0 -0
  121. {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(('.html', '.htm')):
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 = 'file://' + os.path.abspath(path)
32
- self.report_action(tr("📖 Opening HTML file in browser: {path}", path=path), ReportAction.READ)
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(tr("❗ Error opening HTML file: {path}: {err}", path=path, err=str(err)))
37
- return tr("Warning: Error opening HTML file: {path}: {err}", path=path, err=str(err))
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)
@@ -15,6 +15,7 @@ class OpenUrlTool(ToolBase):
15
15
  Returns:
16
16
  str: Status message indicating the result.
17
17
  """
18
+
18
19
  permissions = ToolPermissions(read=True)
19
20
  tool_name = "open_url"
20
21
 
@@ -21,6 +21,7 @@ class PythonCodeRunTool(ToolBase):
21
21
  Returns:
22
22
  str: Output and status message, or file paths/line counts if output is large.
23
23
  """
24
+
24
25
  permissions = ToolPermissions(execute=True)
25
26
  tool_name = "python_code_run"
26
27
 
@@ -21,6 +21,7 @@ class PythonCommandRunTool(ToolBase):
21
21
  Returns:
22
22
  str: Output and status message, or file paths/line counts if output is large.
23
23
  """
24
+
24
25
  permissions = ToolPermissions(execute=True)
25
26
  tool_name = "python_command_run"
26
27
 
@@ -21,6 +21,7 @@ class PythonFileRunTool(ToolBase):
21
21
  Returns:
22
22
  str: Output and status message, or file paths/line counts if output is large.
23
23
  """
24
+
24
25
  permissions = ToolPermissions(execute=True)
25
26
  tool_name = "python_file_run"
26
27
 
@@ -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(tr("📖 Read '{disp_path}'", disp_path=disp_path), ReportAction.READ)
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(tr("❗ not found: {disp_path}", disp_path=disp_path))
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(tr(" ❌ Error reading {disp_path}: {error}", disp_path=disp_path, error=e))
39
- results.append(f"--- File: {disp_path} (error) ---\nError reading file: {e}\n")
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)
@@ -21,6 +21,7 @@ class RemoveDirectoryTool(ToolBase):
21
21
  - "Directory removed: /path/to/dir"
22
22
  - "Error removing directory: <error message>"
23
23
  """
24
+
24
25
  permissions = ToolPermissions(write=True)
25
26
  tool_name = "remove_directory"
26
27
 
@@ -21,6 +21,7 @@ class RemoveFileTool(ToolBase):
21
21
  - " Successfully removed the file at ..."
22
22
  - " Cannot remove file: ..."
23
23
  """
24
+
24
25
  permissions = ToolPermissions(write=True)
25
26
  tool_name = "remove_file"
26
27
 
@@ -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
- path, warning, match_info, details
90
- ) + (f"\n{validation_result}" if validation_result else "")
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)
@@ -24,6 +24,7 @@ class RunBashCommandTool(ToolBase):
24
24
  Returns:
25
25
  str: File paths and line counts for stdout and stderr.
26
26
  """
27
+
27
28
  permissions = ToolPermissions(execute=True)
28
29
  tool_name = "run_bash_command"
29
30
 
@@ -26,6 +26,7 @@ class RunPowershellCommandTool(ToolBase):
26
26
  Returns:
27
27
  str: Output and status message, or file paths/line counts if output is large.
28
28
  """
29
+
29
30
  permissions = ToolPermissions(execute=True)
30
31
  tool_name = "run_powershell_command"
31
32
 
@@ -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 pattern (regex or plain string) in all files within one or more directories or file paths and return matching lines or counts. Respects .gitignore.
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
- pattern (str): Regex pattern or plain text substring to search for in files. Must not be empty. Tries regex first, falls back to substring if regex is invalid.
22
- is_regex (bool): If True, treat pattern as a regular expression. If False, treat as plain text (default).
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
- pattern,
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
- pattern,
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
- pattern,
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
- pattern,
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} '{pattern}' in '{disp_path}'",
91
+ "🔍 Search {search_type} '{query}' in '{disp_path}'",
91
92
  search_type=("regex" if use_regex else "text"),
92
- pattern=pattern,
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
- pattern,
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
- pattern,
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
- pattern,
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
- pattern: str,
158
- is_regex: bool = False,
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
- pattern, is_regex, case_sensitive, self.report_error, self.report_warning
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
- pattern,
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
- pattern,
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, pattern, regex, use_regex, case_sensitive):
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 pattern.lower() in line.lower()
28
- return pattern in line
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
- pattern,
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, pattern, regex, use_regex, case_sensitive):
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
- pattern, use_regex, output, limit_reached, count_only=False, per_file_counts=None
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
- pattern,
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
- pattern,
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
- pattern,
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
- pattern,
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
- pattern,
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
- pattern,
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
- pattern,
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
- from lxml import etree
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()
@@ -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(path: str, workdir: str | None) -> bool: # noqa: D401 – we start with an imperative verb # noqa: D401 – we start with an imperative verb
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("format") == "path"
134
- or (
135
- v.get("type") == "string"
136
- and (
137
- "path" in v.get("description", "").lower()
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(key: str, path: str, workdir: str) -> None: # noqa: D401
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 = (
@@ -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='r' in perm_str,
10
- write='w' in perm_str,
11
- execute='x' in perm_str,
10
+ read="r" in perm_str,
11
+ write="w" in perm_str,
12
+ execute="x" in perm_str,
12
13
  )