janito 1.6.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 (117) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/config.py +3 -3
  3. janito/agent/config_defaults.py +3 -2
  4. janito/agent/conversation.py +73 -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 -6
  15. janito/agent/openai_schema_generator.py +23 -4
  16. janito/agent/platform_discovery.py +90 -0
  17. janito/agent/profile_manager.py +34 -110
  18. janito/agent/queued_message_handler.py +22 -3
  19. janito/agent/rich_message_handler.py +3 -1
  20. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
  21. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
  22. janito/agent/test_handler_protocols.py +47 -0
  23. janito/agent/tests/__init__.py +1 -0
  24. janito/agent/tool_base.py +1 -1
  25. janito/agent/tool_executor.py +109 -0
  26. janito/agent/tool_registry.py +3 -75
  27. janito/agent/tool_use_tracker.py +46 -0
  28. janito/agent/tools/__init__.py +11 -8
  29. janito/agent/tools/ask_user.py +26 -12
  30. janito/agent/tools/create_directory.py +50 -18
  31. janito/agent/tools/create_file.py +60 -29
  32. janito/agent/tools/dir_walk_utils.py +16 -0
  33. janito/agent/tools/fetch_url.py +10 -11
  34. janito/agent/tools/find_files.py +49 -40
  35. janito/agent/tools/get_lines.py +60 -25
  36. janito/agent/tools/memory.py +48 -0
  37. janito/agent/tools/move_file.py +72 -23
  38. janito/agent/tools/outline_file/__init__.py +85 -0
  39. janito/agent/tools/outline_file/formatting.py +20 -0
  40. janito/agent/tools/outline_file/markdown_outline.py +14 -0
  41. janito/agent/tools/outline_file/python_outline.py +71 -0
  42. janito/agent/tools/present_choices.py +62 -0
  43. janito/agent/tools/present_choices_test.py +18 -0
  44. janito/agent/tools/remove_directory.py +31 -26
  45. janito/agent/tools/remove_file.py +31 -13
  46. janito/agent/tools/replace_text_in_file.py +135 -36
  47. janito/agent/tools/run_bash_command.py +113 -97
  48. janito/agent/tools/run_powershell_command.py +169 -0
  49. janito/agent/tools/run_python_command.py +53 -29
  50. janito/agent/tools/search_outline.py +17 -0
  51. janito/agent/tools/search_text.py +208 -0
  52. janito/agent/tools/tools_utils.py +47 -4
  53. janito/agent/tools/utils.py +14 -15
  54. janito/agent/tools/validate_file_syntax.py +163 -0
  55. janito/cli/_print_config.py +1 -1
  56. janito/cli/arg_parser.py +36 -4
  57. janito/cli/config_commands.py +1 -1
  58. janito/cli/logging_setup.py +7 -2
  59. janito/cli/main.py +97 -3
  60. janito/cli/runner/__init__.py +0 -2
  61. janito/cli/runner/_termweb_log_utils.py +17 -0
  62. janito/cli/runner/cli_main.py +121 -89
  63. janito/cli/runner/config.py +6 -4
  64. janito/cli/termweb_starter.py +73 -0
  65. janito/cli_chat_shell/chat_loop.py +52 -13
  66. janito/cli_chat_shell/chat_state.py +1 -1
  67. janito/cli_chat_shell/chat_ui.py +2 -3
  68. janito/cli_chat_shell/commands/__init__.py +17 -6
  69. janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
  70. janito/cli_chat_shell/commands/lang.py +16 -0
  71. janito/cli_chat_shell/commands/prompt.py +42 -0
  72. janito/cli_chat_shell/commands/session_control.py +36 -1
  73. janito/cli_chat_shell/commands/sum.py +49 -0
  74. janito/cli_chat_shell/commands/termweb_log.py +86 -0
  75. janito/cli_chat_shell/commands/utility.py +5 -2
  76. janito/cli_chat_shell/commands/verbose.py +29 -0
  77. janito/cli_chat_shell/load_prompt.py +47 -8
  78. janito/cli_chat_shell/session_manager.py +9 -1
  79. janito/cli_chat_shell/shell_command_completer.py +20 -0
  80. janito/cli_chat_shell/ui.py +110 -93
  81. janito/i18n/__init__.py +35 -0
  82. janito/i18n/messages.py +23 -0
  83. janito/i18n/pt.py +46 -0
  84. janito/rich_utils.py +43 -43
  85. janito/termweb/app.py +95 -0
  86. janito/termweb/static/editor.html +238 -0
  87. janito/termweb/static/editor.html.bak +238 -0
  88. janito/termweb/static/explorer.html.bak +59 -0
  89. janito/termweb/static/favicon.ico +0 -0
  90. janito/termweb/static/favicon.ico.bak +0 -0
  91. janito/termweb/static/index.html +55 -0
  92. janito/termweb/static/index.html.bak +55 -0
  93. janito/termweb/static/index.html.bak.bak +175 -0
  94. janito/termweb/static/landing.html.bak +36 -0
  95. janito/termweb/static/termicon.svg +1 -0
  96. janito/termweb/static/termweb.css +235 -0
  97. janito/termweb/static/termweb.css.bak +286 -0
  98. janito/termweb/static/termweb.js +187 -0
  99. janito/termweb/static/termweb.js.bak +187 -0
  100. janito/termweb/static/termweb.js.bak.bak +157 -0
  101. janito/termweb/static/termweb_quickopen.js +135 -0
  102. janito/termweb/static/termweb_quickopen.js.bak +125 -0
  103. janito/web/app.py +10 -13
  104. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/METADATA +73 -32
  105. janito-1.8.0.dist-info/RECORD +127 -0
  106. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
  107. janito/agent/tool_registry_core.py +0 -2
  108. janito/agent/tools/get_file_outline.py +0 -117
  109. janito/agent/tools/py_compile_file.py +0 -40
  110. janito/agent/tools/replace_file.py +0 -51
  111. janito/agent/tools/search_files.py +0 -71
  112. janito/cli/runner/scan.py +0 -44
  113. janito/cli_chat_shell/commands/system.py +0 -73
  114. janito-1.6.0.dist-info/RECORD +0 -81
  115. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
  116. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
  117. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
@@ -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}
@@ -34,12 +36,24 @@ class AskUserTool(ToolBase):
34
36
  def _(event):
35
37
  pass
36
38
 
39
+ # F12 instruction rotation
40
+ _f12_instructions = [
41
+ tr("proceed"),
42
+ tr("go ahead"),
43
+ tr("continue"),
44
+ tr("next"),
45
+ tr("okay"),
46
+ ]
47
+ _f12_index = {"value": 0}
48
+
37
49
  @bindings.add("f12")
38
50
  def _(event):
39
- """When F12 is pressed, send 'proceed' as input immediately."""
51
+ """When F12 is pressed, rotate through a set of short instructions."""
40
52
  buf = event.app.current_buffer
41
- buf.text = "proceed"
53
+ idx = _f12_index["value"]
54
+ buf.text = _f12_instructions[idx]
42
55
  buf.validate_and_handle()
56
+ _f12_index["value"] = (idx + 1) % len(_f12_instructions)
43
57
 
44
58
  style = Style.from_dict(
45
59
  {
@@ -50,7 +64,7 @@ class AskUserTool(ToolBase):
50
64
  )
51
65
 
52
66
  def get_toolbar():
53
- f12_hint = " Press <b>F12</b> to auto-fill 'proceed' and submit."
67
+ f12_hint = ""
54
68
  if mode["multiline"]:
55
69
  return HTML(
56
70
  f"<b>Multiline mode (Esc+Enter to submit). Type /single to switch.</b>{f12_hint}"
@@ -1,33 +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
5
+ import os
4
6
 
5
7
 
6
8
  @register_tool(name="create_directory")
7
9
  class CreateDirectoryTool(ToolBase):
8
10
  """
9
- Create a new directory at the specified path.
10
-
11
+ Create a new directory at the specified file_path.
11
12
  Args:
12
- path (str): Path for the new directory.
13
- overwrite (bool, optional): Whether to overwrite if the directory exists. Defaults to False.
13
+ file_path (str): Path for the new directory.
14
14
  Returns:
15
15
  str: Status message indicating the result. Example:
16
- - "\u2705 Successfully created the directory at ..."
17
- - "\u2757 Cannot create directory: ..."
16
+ - "✅ Successfully created the directory at ..."
17
+ - "❗ Cannot create directory: ..."
18
18
  """
19
19
 
20
- def call(self, path: str, overwrite: bool = False) -> str:
21
- original_path = path
22
- path = expand_path(path)
23
- disp_path = display_path(original_path, path)
24
- import os
25
-
26
- if os.path.exists(path):
27
- if not os.path.isdir(path):
20
+ def run(self, file_path: str) -> str:
21
+ file_path = expand_path(file_path)
22
+ disp_path = display_path(file_path)
23
+ self.report_info(
24
+ tr("📁 Creating directory: '{disp_path}' ...", disp_path=disp_path)
25
+ )
26
+ try:
27
+ if os.path.exists(file_path):
28
+ if not os.path.isdir(file_path):
29
+ self.report_error(
30
+ tr(
31
+ "❌ Path '{disp_path}' exists and is not a directory.",
32
+ disp_path=disp_path,
33
+ )
34
+ )
35
+ return tr(
36
+ "❌ Path '{disp_path}' exists and is not a directory.",
37
+ disp_path=disp_path,
38
+ )
28
39
  self.report_error(
29
- f"\u274c Path '{disp_path}' exists and is not a directory."
40
+ tr(
41
+ "❗ Directory '{disp_path}' already exists.",
42
+ disp_path=disp_path,
43
+ )
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
+ )
57
+ except Exception as e:
58
+ self.report_error(
59
+ tr(
60
+ "❌ Error creating directory '{disp_path}': {error}",
61
+ disp_path=disp_path,
62
+ error=e,
30
63
  )
31
- return f"\u274c Path '{disp_path}' exists and is not a directory."
32
- # Directory creation logic would go here
33
- return f"\u2705 Successfully created the directory at '{disp_path}'."
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,60 +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 False.
18
- max_depth (int, optional): Maximum directory depth to search (0 = only top-level). If None, unlimited. Defaults to None.
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).
19
19
  Returns:
20
20
  str: Newline-separated list of matching file paths. Example:
21
21
  "/path/to/file1.py\n/path/to/file2.py"
22
22
  "Warning: Empty file pattern provided. Operation skipped."
23
+ If max_results is reached, appends a note to the output.
23
24
  """
24
25
 
25
- def call(
26
- self,
27
- directories: list[str],
28
- pattern: str,
29
- recursive: bool = False,
30
- max_depth: int = None,
31
- ) -> str:
32
- import os
33
-
26
+ def run(self, paths: str, pattern: str, max_depth: int = 0) -> str:
34
27
  if not pattern:
35
28
  self.report_warning(
36
- "âš ī¸ Warning: Empty file pattern provided. Operation skipped."
29
+ tr("âš ī¸ Warning: Empty file pattern provided. Operation skipped.")
37
30
  )
38
- return "Warning: Empty file pattern provided. Operation skipped."
39
- from janito.agent.tools.tools_utils import display_path
40
-
41
- output = []
42
- 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():
43
35
  disp_path = display_path(directory)
44
- self.report_info(f"🔍 Searching for files '{pattern}' in '{disp_path}'")
45
- for root, dirs, files in os.walk(directory):
46
- # Calculate depth
47
- rel_path = os.path.relpath(root, directory)
48
- depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
49
- if max_depth is not None and depth > max_depth:
50
- # Prune traversal
51
- dirs[:] = []
52
- continue
53
- if not recursive and depth > 0:
54
- # Only top-level if not recursive
55
- break
56
- dirs, files = filter_ignored(root, dirs, files)
57
- for filename in fnmatch.filter(files, pattern):
58
- output.append(os.path.join(root, filename))
59
- self.report_success(f" ✅ {len(output)} {pluralize('file', len(output))} found")
60
- 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()
@@ -37,40 +43,69 @@ class GetLinesTool(ToolBase):
37
43
  ]
38
44
  selected_len = len(selected)
39
45
  total_lines = len(lines)
46
+ at_end = False
40
47
  if from_line and to_line:
41
48
  requested = to_line - from_line + 1
42
- if selected_len < requested:
49
+ if to_line >= total_lines or selected_len < requested:
50
+ at_end = True
51
+ if at_end:
43
52
  self.report_success(
44
- f" ✅ {selected_len} {pluralize('line', selected_len)} (end at line {total_lines})"
53
+ tr(
54
+ " ✅ {selected_len} {line_word} (end)",
55
+ selected_len=selected_len,
56
+ line_word=pluralize("line", selected_len),
57
+ )
45
58
  )
46
59
  elif to_line < total_lines:
47
60
  self.report_success(
48
- f" ✅ {selected_len} {pluralize('line', selected_len)} ({total_lines - to_line} lines to end)"
49
- )
50
- else:
51
- self.report_success(
52
- f" ✅ {selected_len} {pluralize('line', selected_len)} (end at line {total_lines})"
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
+ )
53
67
  )
54
68
  else:
55
69
  self.report_success(
56
- 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
+ )
57
75
  )
58
- # Prepare header
59
76
  if from_line and to_line:
60
- header = f"---\nFile: {disp_path} | Lines: {from_line}-{to_line} (of {total_lines})\n---\n"
61
- if to_line >= total_lines:
62
- header = f"---\nFile: {disp_path} | Lines: {from_line}-{to_line} (end at line {total_lines})\n---\n"
77
+ if to_line >= total_lines or selected_len < (to_line - from_line + 1):
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
+ )
84
+ else:
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"
65
- header = f"---\nFile: {disp_path} | Lines: {from_line}-END (end at line {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
+ )
66
99
  else:
67
- header = (
68
- 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,
69
104
  )
70
105
  return header + "".join(selected)
71
106
  except Exception as e:
72
107
  if isinstance(e, FileNotFoundError):
73
- self.report_error("❗ not found")
74
- return "❗ not found"
75
- self.report_error(f" ❌ Error: {e}")
76
- 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)
@@ -0,0 +1,48 @@
1
+ from janito.agent.tool_base import ToolBase
2
+ from janito.agent.tool_registry import register_tool
3
+ from janito.i18n import tr
4
+
5
+
6
+ @register_tool(name="memory")
7
+ class MemoryTool(ToolBase):
8
+ """
9
+ Simple in-memory key-value store for demonstration purposes.
10
+ """
11
+
12
+ def __init__(self):
13
+ super().__init__()
14
+ self.memory = {}
15
+
16
+ def run(self, action: str, key: str, value: str = None) -> str:
17
+ if action == "set":
18
+ self.report_info(tr("â„šī¸ Storing value for key: '{key}' ...", key=key))
19
+ self.memory[key] = value
20
+ msg = tr("Value stored for key: '{key}'.", key=key)
21
+ self.report_success(msg)
22
+ return msg
23
+ elif action == "get":
24
+ self.report_info(tr("â„šī¸ Retrieving value for key: '{key}' ...", key=key))
25
+ if key in self.memory:
26
+ msg = tr(
27
+ "Value for key '{key}': {value}", key=key, value=self.memory[key]
28
+ )
29
+ self.report_success(msg)
30
+ return msg
31
+ else:
32
+ msg = tr("Key '{key}' not found.", key=key)
33
+ self.report_warning(msg)
34
+ return msg
35
+ elif action == "delete":
36
+ if key in self.memory:
37
+ del self.memory[key]
38
+ msg = tr("Key '{key}' deleted.", key=key)
39
+ self.report_success(msg)
40
+ return msg
41
+ else:
42
+ msg = tr("Key '{key}' not found.", key=key)
43
+ self.report_error(msg)
44
+ return msg
45
+ else:
46
+ msg = tr("Unknown action: {action}", action=action)
47
+ self.report_error(msg)
48
+ return msg