janito 1.7.0__py3-none-any.whl β†’ 1.8.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 (115) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/config.py +1 -1
  3. janito/agent/config_defaults.py +2 -2
  4. janito/agent/conversation.py +70 -27
  5. janito/agent/conversation_api.py +104 -4
  6. janito/agent/conversation_exceptions.py +6 -0
  7. janito/agent/conversation_tool_calls.py +17 -3
  8. janito/agent/event.py +24 -0
  9. janito/agent/event_dispatcher.py +24 -0
  10. janito/agent/event_handler_protocol.py +5 -0
  11. janito/agent/event_system.py +15 -0
  12. janito/agent/message_handler.py +4 -1
  13. janito/agent/message_handler_protocol.py +5 -0
  14. janito/agent/openai_client.py +5 -8
  15. janito/agent/openai_schema_generator.py +23 -4
  16. janito/agent/profile_manager.py +15 -83
  17. janito/agent/queued_message_handler.py +22 -3
  18. janito/agent/rich_message_handler.py +66 -72
  19. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
  20. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
  21. janito/agent/test_handler_protocols.py +47 -0
  22. janito/agent/tests/__init__.py +1 -0
  23. janito/agent/tool_base.py +1 -1
  24. janito/agent/tool_executor.py +109 -0
  25. janito/agent/tool_registry.py +3 -75
  26. janito/agent/tool_use_tracker.py +46 -0
  27. janito/agent/tools/__init__.py +8 -9
  28. janito/agent/tools/ask_user.py +19 -11
  29. janito/agent/tools/create_directory.py +43 -28
  30. janito/agent/tools/create_file.py +60 -29
  31. janito/agent/tools/dir_walk_utils.py +16 -0
  32. janito/agent/tools/fetch_url.py +10 -11
  33. janito/agent/tools/find_files.py +49 -32
  34. janito/agent/tools/get_lines.py +54 -18
  35. janito/agent/tools/memory.py +32 -52
  36. janito/agent/tools/move_file.py +72 -23
  37. janito/agent/tools/outline_file/__init__.py +85 -0
  38. janito/agent/tools/outline_file/formatting.py +20 -0
  39. janito/agent/tools/outline_file/markdown_outline.py +14 -0
  40. janito/agent/tools/outline_file/python_outline.py +71 -0
  41. janito/agent/tools/present_choices.py +62 -0
  42. janito/agent/tools/present_choices_test.py +18 -0
  43. janito/agent/tools/remove_directory.py +31 -26
  44. janito/agent/tools/remove_file.py +31 -13
  45. janito/agent/tools/replace_text_in_file.py +135 -36
  46. janito/agent/tools/run_bash_command.py +47 -50
  47. janito/agent/tools/run_powershell_command.py +52 -36
  48. janito/agent/tools/run_python_command.py +49 -29
  49. janito/agent/tools/search_outline.py +17 -0
  50. janito/agent/tools/search_text.py +208 -0
  51. janito/agent/tools/tools_utils.py +47 -4
  52. janito/agent/tools/utils.py +14 -15
  53. janito/agent/tools/validate_file_syntax.py +163 -0
  54. janito/cli/arg_parser.py +36 -4
  55. janito/cli/logging_setup.py +7 -2
  56. janito/cli/main.py +96 -2
  57. janito/cli/runner/_termweb_log_utils.py +17 -0
  58. janito/cli/runner/cli_main.py +119 -77
  59. janito/cli/runner/config.py +2 -2
  60. janito/cli/termweb_starter.py +73 -0
  61. janito/cli_chat_shell/chat_loop.py +42 -7
  62. janito/cli_chat_shell/chat_state.py +1 -1
  63. janito/cli_chat_shell/chat_ui.py +0 -1
  64. janito/cli_chat_shell/commands/__init__.py +15 -6
  65. janito/cli_chat_shell/commands/{history_reset.py β†’ history_start.py} +13 -5
  66. janito/cli_chat_shell/commands/lang.py +16 -0
  67. janito/cli_chat_shell/commands/prompt.py +42 -0
  68. janito/cli_chat_shell/commands/session_control.py +36 -1
  69. janito/cli_chat_shell/commands/termweb_log.py +86 -0
  70. janito/cli_chat_shell/commands/utility.py +5 -2
  71. janito/cli_chat_shell/commands/verbose.py +29 -0
  72. janito/cli_chat_shell/session_manager.py +9 -1
  73. janito/cli_chat_shell/shell_command_completer.py +20 -0
  74. janito/cli_chat_shell/ui.py +110 -99
  75. janito/i18n/__init__.py +35 -0
  76. janito/i18n/messages.py +23 -0
  77. janito/i18n/pt.py +46 -0
  78. janito/rich_utils.py +43 -43
  79. janito/termweb/app.py +95 -0
  80. janito/termweb/static/editor.html +238 -0
  81. janito/termweb/static/editor.html.bak +238 -0
  82. janito/termweb/static/explorer.html.bak +59 -0
  83. janito/termweb/static/favicon.ico +0 -0
  84. janito/termweb/static/favicon.ico.bak +0 -0
  85. janito/termweb/static/index.html +55 -0
  86. janito/termweb/static/index.html.bak +55 -0
  87. janito/termweb/static/index.html.bak.bak +175 -0
  88. janito/termweb/static/landing.html.bak +36 -0
  89. janito/termweb/static/termicon.svg +1 -0
  90. janito/termweb/static/termweb.css +235 -0
  91. janito/termweb/static/termweb.css.bak +286 -0
  92. janito/termweb/static/termweb.js +187 -0
  93. janito/termweb/static/termweb.js.bak +187 -0
  94. janito/termweb/static/termweb.js.bak.bak +157 -0
  95. janito/termweb/static/termweb_quickopen.js +135 -0
  96. janito/termweb/static/termweb_quickopen.js.bak +125 -0
  97. janito/web/app.py +4 -4
  98. {janito-1.7.0.dist-info β†’ janito-1.8.0.dist-info}/METADATA +58 -25
  99. janito-1.8.0.dist-info/RECORD +127 -0
  100. {janito-1.7.0.dist-info β†’ janito-1.8.0.dist-info}/WHEEL +1 -1
  101. janito/agent/templates/profiles/system_prompt_template_base.toml +0 -76
  102. janito/agent/templates/profiles/system_prompt_template_default.toml +0 -3
  103. janito/agent/templates/profiles/system_prompt_template_technical.toml +0 -13
  104. janito/agent/tests/test_prompt_toml.py +0 -61
  105. janito/agent/tool_registry_core.py +0 -2
  106. janito/agent/tools/get_file_outline.py +0 -146
  107. janito/agent/tools/py_compile_file.py +0 -40
  108. janito/agent/tools/replace_file.py +0 -51
  109. janito/agent/tools/search_files.py +0 -65
  110. janito/cli/runner/scan.py +0 -57
  111. janito/cli_chat_shell/commands/system.py +0 -73
  112. janito-1.7.0.dist-info/RECORD +0 -89
  113. {janito-1.7.0.dist-info β†’ janito-1.8.0.dist-info}/entry_points.txt +0 -0
  114. {janito-1.7.0.dist-info β†’ janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
  115. {janito-1.7.0.dist-info β†’ janito-1.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,46 @@
1
+ import threading
2
+ from typing import Any, Dict, List
3
+
4
+
5
+ class ToolUseTracker:
6
+ _instance = None
7
+ _lock = threading.Lock()
8
+
9
+ def __new__(cls):
10
+ if not cls._instance:
11
+ with cls._lock:
12
+ if not cls._instance:
13
+ cls._instance = super().__new__(cls)
14
+ cls._instance._history = []
15
+ return cls._instance
16
+
17
+ def record(self, tool_name: str, params: Dict[str, Any]):
18
+ self._history.append({"tool": tool_name, "params": params})
19
+
20
+ def get_history(self) -> List[Dict[str, Any]]:
21
+ return list(self._history)
22
+
23
+ def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
24
+ ops = []
25
+ for entry in self._history:
26
+ params = entry["params"]
27
+ if any(isinstance(v, str) and file_path in v for v in params.values()):
28
+ ops.append(entry)
29
+ return ops
30
+
31
+ def file_fully_read(self, file_path: str) -> bool:
32
+ for entry in self._history:
33
+ if entry["tool"] == "get_lines":
34
+ params = entry["params"]
35
+ if params.get("file_path") == file_path:
36
+ # If both from_line and to_line are None, full file was read
37
+ if (
38
+ params.get("from_line") is None
39
+ and params.get("to_line") is None
40
+ ):
41
+ return True
42
+ return False
43
+
44
+ @classmethod
45
+ def instance(cls):
46
+ return cls()
@@ -3,11 +3,11 @@ from . import create_directory
3
3
  from . import create_file
4
4
  from . import fetch_url
5
5
  from . import find_files
6
- from . import get_file_outline
7
6
  from . import get_lines
7
+ from . import outline_file
8
8
  from . import gitignore_utils
9
9
  from . import move_file
10
- from . import py_compile_file
10
+ from . import validate_file_syntax
11
11
  from . import remove_directory
12
12
  from . import remove_file
13
13
  from . import replace_text_in_file
@@ -15,10 +15,8 @@ from . import rich_live
15
15
  from . import run_bash_command
16
16
  from . import run_powershell_command
17
17
  from . import run_python_command
18
- from . import search_files
19
- from . import tools_utils
20
- from . import replace_file
21
- from . import memory
18
+ from . import present_choices
19
+ from . import search_text
22
20
 
23
21
  __all__ = [
24
22
  "ask_user",
@@ -26,11 +24,11 @@ __all__ = [
26
24
  "create_file",
27
25
  "fetch_url",
28
26
  "find_files",
29
- "get_file_outline",
27
+ "outline_file",
30
28
  "get_lines",
31
29
  "gitignore_utils",
32
30
  "move_file",
33
- "py_compile_file",
31
+ "validate_file_syntax",
34
32
  "remove_directory",
35
33
  "remove_file",
36
34
  "replace_text_in_file",
@@ -40,6 +38,7 @@ __all__ = [
40
38
  "run_python_command",
41
39
  "search_files",
42
40
  "tools_utils",
43
- "replace_file",
44
41
  "memory",
42
+ "present_choices",
43
+ "search_text",
45
44
  ]
@@ -1,6 +1,15 @@
1
1
  from janito.agent.tool_base import ToolBase
2
2
  from janito.agent.tool_registry import register_tool
3
3
 
4
+ from rich import print as rich_print
5
+ from janito.i18n import tr
6
+ from rich.panel import Panel
7
+ from prompt_toolkit import PromptSession
8
+ from prompt_toolkit.key_binding import KeyBindings
9
+ from prompt_toolkit.enums import EditingMode
10
+ from prompt_toolkit.formatted_text import HTML
11
+ from prompt_toolkit.styles import Style
12
+
4
13
 
5
14
  @register_tool(name="ask_user")
6
15
  class AskUserTool(ToolBase):
@@ -16,16 +25,9 @@ class AskUserTool(ToolBase):
16
25
  - "Some detailed answer..."
17
26
  """
18
27
 
19
- def call(self, question: str) -> str:
20
- from rich import print as rich_print
21
- from rich.panel import Panel
22
- from prompt_toolkit import PromptSession
23
- from prompt_toolkit.key_binding import KeyBindings
24
- from prompt_toolkit.enums import EditingMode
25
- from prompt_toolkit.formatted_text import HTML
26
- from prompt_toolkit.styles import Style
28
+ def run(self, question: str) -> str:
27
29
 
28
- rich_print(Panel.fit(question, title="Question", style="cyan"))
30
+ rich_print(Panel.fit(question, title=tr("Question"), style="cyan"))
29
31
 
30
32
  bindings = KeyBindings()
31
33
  mode = {"multiline": False}
@@ -35,7 +37,13 @@ class AskUserTool(ToolBase):
35
37
  pass
36
38
 
37
39
  # F12 instruction rotation
38
- _f12_instructions = ["proceed", "go ahead", "continue", "next", "okay"]
40
+ _f12_instructions = [
41
+ tr("proceed"),
42
+ tr("go ahead"),
43
+ tr("continue"),
44
+ tr("next"),
45
+ tr("okay"),
46
+ ]
39
47
  _f12_index = {"value": 0}
40
48
 
41
49
  @bindings.add("f12")
@@ -56,7 +64,7 @@ class AskUserTool(ToolBase):
56
64
  )
57
65
 
58
66
  def get_toolbar():
59
- f12_hint = " Press <b>F12</b> to auto-fill 'proceed' and submit."
67
+ f12_hint = ""
60
68
  if mode["multiline"]:
61
69
  return HTML(
62
70
  f"<b>Multiline mode (Esc+Enter to submit). Type /single to switch.</b>{f12_hint}"
@@ -1,50 +1,65 @@
1
1
  from janito.agent.tool_registry import register_tool
2
2
  from janito.agent.tools.utils import expand_path, display_path
3
3
  from janito.agent.tool_base import ToolBase
4
+ from janito.i18n import tr
4
5
  import os
5
- import shutil
6
6
 
7
7
 
8
8
  @register_tool(name="create_directory")
9
9
  class CreateDirectoryTool(ToolBase):
10
10
  """
11
- Create a new directory at the specified path.
12
-
11
+ Create a new directory at the specified file_path.
13
12
  Args:
14
- path (str): Path for the new directory.
15
- overwrite (bool, optional): Whether to overwrite if the directory exists. Defaults to False.
13
+ file_path (str): Path for the new directory.
16
14
  Returns:
17
15
  str: Status message indicating the result. Example:
18
- - "\u2705 Successfully created the directory at ..."
19
- - "\u2757 Cannot create directory: ..."
16
+ - "βœ… Successfully created the directory at ..."
17
+ - "❗ Cannot create directory: ..."
20
18
  """
21
19
 
22
- def call(self, path: str, overwrite: bool = False) -> str:
23
- original_path = path
24
- path = expand_path(path)
25
- disp_path = display_path(original_path, path)
20
+ def run(self, file_path: str) -> str:
21
+ file_path = expand_path(file_path)
22
+ disp_path = display_path(file_path)
26
23
  self.report_info(
27
- f"\U0001f4c1 Creating directory: '{disp_path}' (overwrite={overwrite}) ... "
24
+ tr("πŸ“ Creating directory: '{disp_path}' ...", disp_path=disp_path)
28
25
  )
29
26
  try:
30
- if os.path.exists(path):
31
- if not os.path.isdir(path):
27
+ if os.path.exists(file_path):
28
+ if not os.path.isdir(file_path):
32
29
  self.report_error(
33
- f"\u274c Path '{disp_path}' exists and is not a directory."
30
+ tr(
31
+ "❌ Path '{disp_path}' exists and is not a directory.",
32
+ disp_path=disp_path,
33
+ )
34
34
  )
35
- return f"\u274c Path '{disp_path}' exists and is not a directory."
36
- if not overwrite:
37
- self.report_error(
38
- f"\u2757 Directory '{disp_path}' already exists (overwrite=False)"
35
+ return tr(
36
+ "❌ Path '{disp_path}' exists and is not a directory.",
37
+ disp_path=disp_path,
39
38
  )
40
- return (
41
- f"\u2757 Cannot create directory: '{disp_path}' already exists."
39
+ self.report_error(
40
+ tr(
41
+ "❗ Directory '{disp_path}' already exists.",
42
+ disp_path=disp_path,
42
43
  )
43
- # Overwrite: remove existing directory
44
- shutil.rmtree(path)
45
- os.makedirs(path, exist_ok=True)
46
- self.report_success(f"\u2705 Directory created at '{disp_path}'")
47
- return f"\u2705 Successfully created the directory at '{disp_path}'."
44
+ )
45
+ return tr(
46
+ "❗ Cannot create directory: '{disp_path}' already exists.",
47
+ disp_path=disp_path,
48
+ )
49
+ os.makedirs(file_path, exist_ok=True)
50
+ self.report_success(
51
+ tr("βœ… Directory created at '{disp_path}'", disp_path=disp_path)
52
+ )
53
+ return tr(
54
+ "βœ… Successfully created the directory at '{disp_path}'.",
55
+ disp_path=disp_path,
56
+ )
48
57
  except Exception as e:
49
- self.report_error(f"\u274c Error creating directory '{disp_path}': {e}")
50
- return f"\u274c Cannot create directory: {e}"
58
+ self.report_error(
59
+ tr(
60
+ "❌ Error creating directory '{disp_path}': {error}",
61
+ disp_path=disp_path,
62
+ error=e,
63
+ )
64
+ )
65
+ return tr("❌ Cannot create directory: {error}", error=e)
@@ -3,45 +3,76 @@ import shutil
3
3
  from janito.agent.tool_registry import register_tool
4
4
  from janito.agent.tools.utils import expand_path, display_path
5
5
  from janito.agent.tool_base import ToolBase
6
- from janito.agent.tools.tools_utils import pluralize
6
+ from janito.i18n import tr
7
7
 
8
8
 
9
9
  @register_tool(name="create_file")
10
10
  class CreateFileTool(ToolBase):
11
11
  """
12
- Create a new file with the given content. Fails if the file already exists.
13
-
14
- This tool will NOT overwrite existing files. If the file already exists, the operation fails and no changes are made to the file itself.
15
-
12
+ Create a new file with the given content, or overwrite if specified.
16
13
  Args:
17
- path (str): Path to the file to create.
14
+ file_path (str): Path to the file to create or overwrite.
18
15
  content (str): Content to write to the file.
19
- backup (bool, optional): If True, create a backup (.bak) before returning an error if the file exists. Defaults to False.
16
+ overwrite (bool, optional): If True, overwrite the file if it exists. Defaults to False.
17
+ CRITICAL: If you use overwrite=True, you MUST provide the full content for the file. Using placeholders or partial content will result in file corruption. Before overwriting, read the full original file.
20
18
  Returns:
21
19
  str: Status message indicating the result. Example:
22
- - "\u2705 Successfully created the file at ..."
23
- - "\u2757 Cannot create file: ..."
20
+ - "βœ… Successfully created the file at ..."
24
21
  """
25
22
 
26
- def call(self, path: str, content: str, backup: bool = False) -> str:
27
- original_path = path
28
- path = expand_path(path)
29
- disp_path = display_path(original_path, path)
30
- if os.path.exists(path):
31
- if os.path.isdir(path):
32
- self.report_error("\u274c Error: is a directory")
33
- return f"\u274c Cannot create file: '{disp_path}' is an existing directory."
34
- if backup:
35
- shutil.copy2(path, path + ".bak")
36
- self.report_error(f"\u2757 Error: file '{disp_path}' already exists")
37
- return f"\u2757 Cannot create file: '{disp_path}' already exists."
38
- # Ensure parent directories exist
39
- dir_name = os.path.dirname(path)
40
- if dir_name:
41
- os.makedirs(dir_name, exist_ok=True)
42
- self.report_info(f"\U0001f4dd Creating file: '{disp_path}' ... ")
43
- with open(path, "w", encoding="utf-8", errors="replace") as f:
23
+ def run(self, file_path: str, content: str, overwrite: bool = False) -> str:
24
+ expanded_file_path = expand_path(file_path)
25
+ disp_path = display_path(expanded_file_path)
26
+ file_path = expanded_file_path
27
+ backup_path = None
28
+ if os.path.exists(file_path):
29
+ if not overwrite:
30
+ return tr(
31
+ "⚠️ File already exists at '{disp_path}'. Use overwrite=True to overwrite.",
32
+ disp_path=disp_path,
33
+ )
34
+ # Check ToolUseTracker for full read before overwrite
35
+ try:
36
+ from janito.agent.tool_use_tracker import ToolUseTracker
37
+
38
+ tracker = ToolUseTracker()
39
+ if not tracker.file_fully_read(file_path):
40
+ self.report_error(
41
+ "❌ Refusing to overwrite file: full file has not been read with get_lines."
42
+ )
43
+ return tr(
44
+ "❌ Refusing to overwrite file: full file has not been read with get_lines."
45
+ )
46
+ except Exception as e:
47
+ self.report_error(f"[ToolUseTracker] Error: {e}")
48
+ return tr("[ToolUseTracker] Error: {e}")
49
+ backup_path = file_path + ".bak"
50
+ shutil.copy2(file_path, backup_path)
51
+ self.report_info(
52
+ tr("πŸ“ Updating file: '{disp_path}' ...", disp_path=disp_path)
53
+ )
54
+ mode = "w"
55
+ updated = True
56
+ else:
57
+ dir_name = os.path.dirname(file_path)
58
+ if dir_name:
59
+ os.makedirs(dir_name, exist_ok=True)
60
+ self.report_info(
61
+ tr("πŸ“ Creating file: '{disp_path}' ...", disp_path=disp_path)
62
+ )
63
+ mode = "w"
64
+ updated = False
65
+ with open(file_path, mode, encoding="utf-8", errors="replace") as f:
44
66
  f.write(content)
45
67
  new_lines = content.count("\n") + 1 if content else 0
46
- self.report_success(f"\u2705 {new_lines} {pluralize('line', new_lines)}")
47
- return f"\u2705 Successfully created the file at '{disp_path}' ({new_lines} lines)."
68
+ if updated:
69
+ self.report_success(tr("βœ… ({new_lines} lines).", new_lines=new_lines))
70
+ msg = tr(
71
+ "βœ… Updated file ({new_lines} lines, backup at {backup_path}).",
72
+ new_lines=new_lines,
73
+ backup_path=backup_path,
74
+ )
75
+ return msg
76
+ else:
77
+ self.report_success(tr("βœ… ({new_lines} lines).", new_lines=new_lines))
78
+ return tr("βœ… Created file ({new_lines} lines).", new_lines=new_lines)
@@ -0,0 +1,16 @@
1
+ import os
2
+ from janito.agent.tools.gitignore_utils import filter_ignored
3
+
4
+
5
+ def walk_dir_with_gitignore(root_dir, max_depth=0):
6
+ """
7
+ Walks the directory tree starting at root_dir, yielding (root, dirs, files) tuples,
8
+ with .gitignore rules applied. If max_depth > 0, limits recursion to that depth.
9
+ """
10
+ for root, dirs, files in os.walk(root_dir):
11
+ rel_path = os.path.relpath(root, root_dir)
12
+ depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
13
+ if max_depth > 0 and depth > max_depth:
14
+ continue
15
+ dirs, files = filter_ignored(root, dirs, files)
16
+ yield root, dirs, files
@@ -1,15 +1,14 @@
1
1
  import requests
2
2
  from bs4 import BeautifulSoup
3
3
  from janito.agent.tool_registry import register_tool
4
-
5
4
  from janito.agent.tool_base import ToolBase
5
+ from janito.i18n import tr
6
6
 
7
7
 
8
8
  @register_tool(name="fetch_url")
9
9
  class FetchUrlTool(ToolBase):
10
10
  """
11
11
  Fetch the content of a web page and extract its text.
12
-
13
12
  Args:
14
13
  url (str): The URL of the web page to fetch.
15
14
  search_strings (list[str], optional): Strings to search for in the page content.
@@ -20,22 +19,23 @@ class FetchUrlTool(ToolBase):
20
19
  - "Warning: Empty URL provided. Operation skipped."
21
20
  """
22
21
 
23
- def call(self, url: str, search_strings: list[str] = None) -> str:
22
+ def run(self, url: str, search_strings: list[str] = None) -> str:
24
23
  if not url.strip():
25
- self.report_warning("⚠️ Warning: Empty URL provided. Operation skipped.")
26
- return "Warning: Empty URL provided. Operation skipped."
27
- self.report_info(f"🌐 Fetching URL: {url} ... ")
24
+ self.report_warning(tr("⚠️ Warning: Empty URL provided. Operation skipped."))
25
+ return tr("Warning: Empty URL provided. Operation skipped.")
26
+ self.report_info(tr("🌐 Fetching URL: {url} ...", url=url))
28
27
  response = requests.get(url, timeout=10)
29
28
  response.raise_for_status()
30
29
  self.update_progress(
31
30
  {
32
31
  "event": "progress",
33
- "message": f"Fetched URL with status {response.status_code}",
32
+ "message": tr(
33
+ "Fetched URL with status {status}", status=response.status_code
34
+ ),
34
35
  }
35
36
  )
36
37
  soup = BeautifulSoup(response.text, "html.parser")
37
38
  text = soup.get_text(separator="\n")
38
-
39
39
  if search_strings:
40
40
  filtered = []
41
41
  for s in search_strings:
@@ -48,7 +48,6 @@ class FetchUrlTool(ToolBase):
48
48
  if filtered:
49
49
  text = "\n...\n".join(filtered)
50
50
  else:
51
- text = "No lines found for the provided search strings."
52
-
53
- self.report_success("βœ… Result")
51
+ text = tr("No lines found for the provided search strings.")
52
+ self.report_success(tr("βœ… Result"))
54
53
  return text
@@ -1,52 +1,69 @@
1
1
  from janito.agent.tool_base import ToolBase
2
2
  from janito.agent.tool_registry import register_tool
3
- from janito.agent.tools.tools_utils import pluralize
4
-
3
+ from janito.agent.tools.tools_utils import pluralize, display_path
4
+ from janito.agent.tools.dir_walk_utils import walk_dir_with_gitignore
5
+ from janito.i18n import tr
5
6
  import fnmatch
6
- from janito.agent.tools.gitignore_utils import filter_ignored
7
+ import os
7
8
 
8
9
 
9
10
  @register_tool(name="find_files")
10
11
  class FindFilesTool(ToolBase):
11
12
  """
12
13
  Find files in one or more directories matching a pattern. Respects .gitignore.
13
-
14
14
  Args:
15
- directories (list[str]): List of directories to search in.
16
- pattern (str): File pattern to match. Uses Unix shell-style wildcards (fnmatch), e.g. '*.py', 'data_??.csv', '[a-z]*.txt'.
17
- recursive (bool, optional): Whether to search recursively in subdirectories. Defaults to True.
15
+ paths (str): String of one or more paths (space-separated) to search in. Each path can be a directory.
16
+ pattern (str): File pattern(s) to match. Multiple patterns can be separated by spaces. Uses Unix shell-style wildcards (fnmatch), e.g. '*.py', 'data_??.csv', '[a-z]*.txt'.
17
+ 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).
18
+ max_results (int, optional): Maximum number of results to return. 0 means no limit (default).
18
19
  Returns:
19
20
  str: Newline-separated list of matching file paths. Example:
20
21
  "/path/to/file1.py\n/path/to/file2.py"
21
22
  "Warning: Empty file pattern provided. Operation skipped."
23
+ If max_results is reached, appends a note to the output.
22
24
  """
23
25
 
24
- def call(
25
- self,
26
- directories: list[str],
27
- pattern: str,
28
- recursive: bool = True,
29
- ) -> str:
30
- import os
31
-
26
+ def run(self, paths: str, pattern: str, max_depth: int = 0) -> str:
32
27
  if not pattern:
33
28
  self.report_warning(
34
- "⚠️ Warning: Empty file pattern provided. Operation skipped."
29
+ tr("⚠️ Warning: Empty file pattern provided. Operation skipped.")
35
30
  )
36
- return "Warning: Empty file pattern provided. Operation skipped."
37
- from janito.agent.tools.tools_utils import display_path
38
-
39
- output = []
40
- for directory in directories:
31
+ return tr("Warning: Empty file pattern provided. Operation skipped.")
32
+ output = set()
33
+ patterns = pattern.split()
34
+ for directory in paths.split():
41
35
  disp_path = display_path(directory)
42
- self.report_info(f"πŸ” Searching for files '{pattern}' in '{disp_path}'")
43
- for root, dirs, files in os.walk(directory):
44
- rel_path = os.path.relpath(root, directory)
45
- depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
46
- if not recursive and depth > 0:
47
- break
48
- dirs, files = filter_ignored(root, dirs, files)
49
- for filename in fnmatch.filter(files, pattern):
50
- output.append(os.path.join(root, filename))
51
- self.report_success(f" βœ… {len(output)} {pluralize('file', len(output))} found")
52
- return "\n".join(output)
36
+ depth_msg = (
37
+ tr(" (max depth: {max_depth})", max_depth=max_depth)
38
+ if max_depth > 0
39
+ else ""
40
+ )
41
+ self.report_info(
42
+ tr(
43
+ "πŸ” Searching for files '{pattern}' in '{disp_path}'{depth_msg} ...",
44
+ pattern=pattern,
45
+ disp_path=disp_path,
46
+ depth_msg=depth_msg,
47
+ )
48
+ )
49
+ for root, dirs, files in walk_dir_with_gitignore(
50
+ directory, max_depth=max_depth
51
+ ):
52
+ for pat in patterns:
53
+ for filename in fnmatch.filter(files, pat):
54
+ output.add(os.path.join(root, filename))
55
+ self.report_success(
56
+ tr(
57
+ " βœ… {count} {file_word} found",
58
+ count=len(output),
59
+ file_word=pluralize("file", len(output)),
60
+ )
61
+ )
62
+ # If searching in '.', strip leading './' from results
63
+ if paths.strip() == ".":
64
+ output = {
65
+ p[2:] if (p.startswith("./") or p.startswith(".\\")) else p
66
+ for p in output
67
+ }
68
+ result = "\n".join(sorted(output))
69
+ return result
@@ -1,13 +1,13 @@
1
1
  from janito.agent.tool_base import ToolBase
2
2
  from janito.agent.tool_registry import register_tool
3
3
  from janito.agent.tools.tools_utils import pluralize
4
+ from janito.i18n import tr
4
5
 
5
6
 
6
7
  @register_tool(name="get_lines")
7
8
  class GetLinesTool(ToolBase):
8
9
  """
9
10
  Read lines from a file. Returns specific lines if a range is provided, or the entire file if no range is given. If both from_line and to_line are None, the entire file is returned in one callβ€”no need to chunk or split requests when reading the full file.
10
-
11
11
  Args:
12
12
  file_path (str): Path to the file to read lines from.
13
13
  from_line (int, optional): Starting line number (1-based). If None, starts from the first line.
@@ -20,15 +20,21 @@ class GetLinesTool(ToolBase):
20
20
  - "❗ not found"
21
21
  """
22
22
 
23
- def call(self, file_path: str, from_line: int = None, to_line: int = None) -> str:
23
+ def run(self, file_path: str, from_line: int = None, to_line: int = None) -> str:
24
24
  from janito.agent.tools.tools_utils import display_path
25
25
 
26
26
  disp_path = display_path(file_path)
27
27
  if from_line and to_line:
28
- self.report_info(f"πŸ“„ Reading {disp_path} lines {from_line}-{to_line}")
28
+ self.report_info(
29
+ tr(
30
+ "πŸ“– Reading {disp_path} {from_line}-{to_line}",
31
+ disp_path=disp_path,
32
+ from_line=from_line,
33
+ to_line=to_line,
34
+ )
35
+ )
29
36
  else:
30
- self.report_info(f"πŸ“„ Reading {disp_path} (all lines)")
31
-
37
+ self.report_info(tr("πŸ“– Reading {disp_path} all", disp_path=disp_path))
32
38
  try:
33
39
  with open(file_path, "r", encoding="utf-8", errors="replace") as f:
34
40
  lines = f.readlines()
@@ -44,32 +50,62 @@ class GetLinesTool(ToolBase):
44
50
  at_end = True
45
51
  if at_end:
46
52
  self.report_success(
47
- f" βœ… {selected_len} {pluralize('line', selected_len)} (end)"
53
+ tr(
54
+ " βœ… {selected_len} {line_word} (end)",
55
+ selected_len=selected_len,
56
+ line_word=pluralize("line", selected_len),
57
+ )
48
58
  )
49
59
  elif to_line < total_lines:
50
60
  self.report_success(
51
- f" βœ… {selected_len} {pluralize('line', selected_len)} ({total_lines - to_line} lines to end)"
61
+ tr(
62
+ " βœ… {selected_len} {line_word} ({remaining} to eof)",
63
+ selected_len=selected_len,
64
+ line_word=pluralize("line", selected_len),
65
+ remaining=total_lines - to_line,
66
+ )
52
67
  )
53
68
  else:
54
69
  self.report_success(
55
- f" βœ… {selected_len} {pluralize('line', selected_len)} (full file)"
70
+ tr(
71
+ " βœ… {selected_len} {line_word}",
72
+ selected_len=selected_len,
73
+ line_word=pluralize("line", selected_len),
74
+ )
56
75
  )
57
- # Prepare header
58
76
  if from_line and to_line:
59
77
  if to_line >= total_lines or selected_len < (to_line - from_line + 1):
60
- header = f"---\nFile: {disp_path} | Lines: {from_line}-{to_line} (end)\n---\n"
78
+ header = tr(
79
+ "---\n{disp_path} {from_line}-{to_line} (end)\n---\n",
80
+ disp_path=disp_path,
81
+ from_line=from_line,
82
+ to_line=to_line,
83
+ )
61
84
  else:
62
- header = f"---\nFile: {disp_path} | Lines: {from_line}-{to_line} (of {total_lines})\n---\n"
85
+ header = tr(
86
+ "---\n{disp_path} {from_line}-{to_line} (of {total_lines})\n---\n",
87
+ disp_path=disp_path,
88
+ from_line=from_line,
89
+ to_line=to_line,
90
+ total_lines=total_lines,
91
+ )
63
92
  elif from_line:
64
- header = f"---\nFile: {disp_path} | Lines: {from_line}-END (of {total_lines})\n---\n"
93
+ header = tr(
94
+ "---\n{disp_path} {from_line}-END (of {total_lines})\n---\n",
95
+ disp_path=disp_path,
96
+ from_line=from_line,
97
+ total_lines=total_lines,
98
+ )
65
99
  else:
66
- header = (
67
- f"---\nFile: {disp_path} | All lines (total: {total_lines})\n---\n"
100
+ header = tr(
101
+ "---\n{disp_path} All lines (total: {total_lines})\n---\n",
102
+ disp_path=disp_path,
103
+ total_lines=total_lines,
68
104
  )
69
105
  return header + "".join(selected)
70
106
  except Exception as e:
71
107
  if isinstance(e, FileNotFoundError):
72
- self.report_error("❗ not found")
73
- return "❗ not found"
74
- self.report_error(f" ❌ Error: {e}")
75
- return f"Error reading file: {e}"
108
+ self.report_error(tr("❗ not found"))
109
+ return tr("❗ not found")
110
+ self.report_error(tr(" ❌ Error: {error}", error=e))
111
+ return tr("Error reading file: {error}", error=e)