janito 1.4.0__py3-none-any.whl → 1.5.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 (58) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/__init__.py +0 -1
  3. janito/agent/agent.py +7 -25
  4. janito/agent/config.py +4 -6
  5. janito/agent/config_defaults.py +2 -2
  6. janito/agent/content_handler.py +0 -0
  7. janito/agent/conversation.py +63 -37
  8. janito/agent/message_handler.py +18 -0
  9. janito/agent/openai_schema_generator.py +116 -0
  10. janito/agent/queued_message_handler.py +32 -0
  11. janito/agent/rich_tool_handler.py +43 -0
  12. janito/agent/runtime_config.py +1 -1
  13. janito/agent/templates/system_instructions.j2 +10 -4
  14. janito/agent/tool_registry.py +92 -0
  15. janito/agent/tools/append_text_to_file.py +41 -0
  16. janito/agent/tools/ask_user.py +16 -3
  17. janito/agent/tools/create_directory.py +31 -0
  18. janito/agent/tools/create_file.py +52 -0
  19. janito/agent/tools/fetch_url.py +23 -8
  20. janito/agent/tools/find_files.py +40 -21
  21. janito/agent/tools/get_file_outline.py +26 -8
  22. janito/agent/tools/get_lines.py +53 -19
  23. janito/agent/tools/move_file.py +50 -0
  24. janito/agent/tools/py_compile.py +27 -11
  25. janito/agent/tools/python_exec.py +43 -14
  26. janito/agent/tools/remove_directory.py +23 -7
  27. janito/agent/tools/remove_file.py +38 -0
  28. janito/agent/tools/replace_text_in_file.py +40 -17
  29. janito/agent/tools/run_bash_command.py +107 -80
  30. janito/agent/tools/search_files.py +38 -19
  31. janito/agent/tools/tool_base.py +30 -3
  32. janito/agent/tools/tools_utils.py +11 -0
  33. janito/agent/tools/utils.py +0 -1
  34. janito/cli/_print_config.py +1 -1
  35. janito/cli/arg_parser.py +2 -1
  36. janito/cli/config_commands.py +3 -6
  37. janito/cli/main.py +2 -2
  38. janito/cli/runner.py +18 -14
  39. janito/cli_chat_shell/chat_loop.py +10 -15
  40. janito/cli_chat_shell/commands.py +8 -3
  41. janito/cli_chat_shell/config_shell.py +0 -3
  42. janito/cli_chat_shell/session_manager.py +11 -0
  43. janito/cli_chat_shell/ui.py +12 -113
  44. janito/render_prompt.py +0 -1
  45. janito/rich_utils.py +30 -0
  46. janito/web/app.py +10 -12
  47. janito-1.5.0.dist-info/METADATA +176 -0
  48. janito-1.5.0.dist-info/RECORD +64 -0
  49. janito/agent/queued_tool_handler.py +0 -16
  50. janito/agent/tool_handler.py +0 -196
  51. janito/agent/tools/file_ops.py +0 -114
  52. janito/agent/tools/rich_utils.py +0 -31
  53. janito-1.4.0.dist-info/METADATA +0 -142
  54. janito-1.4.0.dist-info/RECORD +0 -55
  55. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/WHEEL +0 -0
  56. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/entry_points.txt +0 -0
  57. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/licenses/LICENSE +0 -0
  58. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/top_level.txt +0 -0
@@ -1,24 +1,40 @@
1
1
  from janito.agent.tools.tool_base import ToolBase
2
- from janito.agent.tool_handler import ToolHandler
2
+ from janito.agent.tool_registry import register_tool
3
+
3
4
  import shutil
4
5
  import os
5
6
 
6
- from janito.agent.tools.rich_utils import print_info, print_success, print_error
7
7
 
8
+
9
+ @register_tool(name="remove_directory")
8
10
  class RemoveDirectoryTool(ToolBase):
9
11
  """Remove a directory. If recursive=False and directory not empty, raises error."""
10
12
  def call(self, directory: str, recursive: bool = False) -> str:
11
- print_info(f"🗃️ Removing directory: {directory} (recursive={recursive})")
12
- self.update_progress(f"Removing directory: {directory} (recursive={recursive})")
13
+ """
14
+ Remove a directory.
15
+
16
+ Args:
17
+ directory (str): Path to the directory to remove.
18
+ recursive (bool, optional): Remove recursively if True. Defaults to False.
19
+
20
+ Returns:
21
+ str: Status message indicating result. Example:
22
+ - "Directory removed: /path/to/dir"
23
+ - "Error removing directory: <error message>"
24
+ """
25
+ self.report_info(f"🗃️ Removing directory: {directory} (recursive={recursive})")
26
+
13
27
  try:
14
28
  if recursive:
15
29
  shutil.rmtree(directory)
16
30
  else:
17
31
  os.rmdir(directory)
18
- print_success(f"\u2705 Directory removed: {directory}")
32
+
33
+ self.report_success(f"✅ 1 {pluralize('directory', 1)}")
19
34
  return f"Directory removed: {directory}"
20
35
  except Exception as e:
21
- print_error(f"\u274c Error removing directory: {e}")
36
+ self.report_error(f" Error removing directory: {e}")
22
37
  return f"Error removing directory: {e}"
23
38
 
24
- ToolHandler.register_tool(RemoveDirectoryTool, name="remove_directory")
39
+
40
+ from janito.agent.tools.tools_utils import pluralize
@@ -0,0 +1,38 @@
1
+ import os
2
+ from janito.agent.tool_registry import register_tool
3
+ from janito.agent.tools.utils import expand_path, display_path
4
+ from janito.agent.tools.tool_base import ToolBase
5
+
6
+ @register_tool(name="remove_file")
7
+ class RemoveFileTool(ToolBase):
8
+ """
9
+ Remove a file at the specified path.
10
+ """
11
+ def call(self, file_path: str) -> str:
12
+ """
13
+ Remove a file from the filesystem.
14
+
15
+ Args:
16
+ file_path (str): Path to the file to remove.
17
+
18
+ Returns:
19
+ str: Status message indicating the result. Example:
20
+ - "✅ Successfully removed the file at ..."
21
+ - "❗ Cannot remove file: ..."
22
+ """
23
+ original_path = file_path
24
+ path = expand_path(file_path)
25
+ disp_path = display_path(original_path, path)
26
+ if not os.path.exists(path):
27
+ self.report_error(f"❌ File '{disp_path}' does not exist.")
28
+ return f"❌ File '{disp_path}' does not exist."
29
+ if not os.path.isfile(path):
30
+ self.report_error(f"❌ Path '{disp_path}' is not a file.")
31
+ return f"❌ Path '{disp_path}' is not a file."
32
+ try:
33
+ os.remove(path)
34
+ self.report_success(f"✅ File removed: '{disp_path}'")
35
+ return f"✅ Successfully removed the file at '{disp_path}'."
36
+ except Exception as e:
37
+ self.report_error(f"❌ Error removing file: {e}")
38
+ return f"❌ Error removing file: {e}"
@@ -1,10 +1,14 @@
1
1
  from janito.agent.tools.tool_base import ToolBase
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
2
+ from janito.agent.tool_registry import register_tool
3
+
4
+ @register_tool(name="replace_text_in_file")
5
+
4
6
 
5
7
  class ReplaceTextInFileTool(ToolBase):
6
8
  """Replace exact occurrences of a given text in a file.
7
9
 
10
+ This tool is designed to make minimal, targeted changes—preferably a small region modifications—rather than rewriting large sections or the entire file. Use it for precise, context-aware edits.
11
+
8
12
  NOTE: Indentation (leading whitespace) must be included in both search_text and replacement_text. This tool does not automatically adjust or infer indentation; matches are exact, including whitespace.
9
13
  """
10
14
  def call(self, file_path: str, search_text: str, replacement_text: str, replace_all: bool = False) -> str:
@@ -17,16 +21,24 @@ NOTE: Indentation (leading whitespace) must be included in both search_text and
17
21
  replacement_text (str): Replacement text. Must include desired indentation (leading whitespace).
18
22
  replace_all (bool): If True, replace all occurrences; otherwise, only the first occurrence.
19
23
  Returns:
20
- str: Status message.
24
+ str: Status message. Example:
25
+ - "Text replaced in /path/to/file"
26
+ - "No changes made. [Warning: Search text not found in file] Please review the original file."
27
+ - "Error replacing text: <error message>"
21
28
  """
22
- import os
23
- filename = os.path.basename(file_path)
24
- action = "all occurrences" if replace_all else "first occurrence"
29
+ from janito.agent.tools.tools_utils import display_path
30
+ disp_path = display_path(file_path)
31
+ action = "all occurrences" if replace_all else None
25
32
  # Show only concise info (lengths, not full content)
26
33
  search_preview = (search_text[:20] + '...') if len(search_text) > 20 else search_text
27
34
  replace_preview = (replacement_text[:20] + '...') if len(replacement_text) > 20 else replacement_text
28
- print_info(f"\U0001F4DD Replacing in {filename}: '{search_preview}'  '{replace_preview}' ({action})", end="")
29
- self.update_progress(f"Replacing text in {file_path}")
35
+ search_lines = len(search_text.splitlines())
36
+ replace_lines = len(replacement_text.splitlines())
37
+ info_msg = f"📝 Replacing in {disp_path}: {search_lines}→{replace_lines} lines"
38
+ if action:
39
+ info_msg += f" ({action})"
40
+ self.report_info(info_msg)
41
+
30
42
  try:
31
43
  with open(file_path, 'r', encoding='utf-8') as f:
32
44
  content = f.read()
@@ -37,17 +49,26 @@ NOTE: Indentation (leading whitespace) must be included in both search_text and
37
49
  else:
38
50
  occurrences = content.count(search_text)
39
51
  if occurrences > 1:
40
- print_error(f" ❌ Error: Search text is not unique ({occurrences} occurrences found). Provide more detailed context.")
41
- return f"Error: Search text is not unique ({occurrences} occurrences found) in {file_path}. Provide more detailed context for unique replacement."
52
+ self.report_warning("⚠️ Search text is not unique.")
53
+ warning_detail = "The search text is not unique. Expand your search context with surrounding lines to ensure uniqueness."
54
+ return f"No changes made. {warning_detail}"
42
55
  replaced_count = 1 if occurrences == 1 else 0
43
56
  new_content = content.replace(search_text, replacement_text, 1)
44
- with open(file_path, 'w', encoding='utf-8') as f:
45
- f.write(new_content)
57
+ if new_content != content:
58
+ with open(file_path, 'w', encoding='utf-8') as f:
59
+ f.write(new_content)
60
+ file_changed = True
61
+ else:
62
+ file_changed = False
46
63
  warning = ''
47
64
  if replaced_count == 0:
48
- warning = f" [Warning: Search text not found in file]"
49
- print_error(warning)
50
- print_success(f" {replaced_count} replaced{warning}")
65
+ warning = " [Warning: Search text not found in file]"
66
+ if not file_changed:
67
+ self.report_warning(" No changes made.")
68
+ concise_warning = "The search text was not found. Expand your search context with surrounding lines if needed."
69
+ return f"No changes made. {concise_warning}"
70
+
71
+ self.report_success(f" ✅ {replaced_count} {pluralize('block', replaced_count)} replaced")
51
72
  # Indentation check for agent warning
52
73
  def leading_ws(line):
53
74
  import re
@@ -58,10 +79,12 @@ NOTE: Indentation (leading whitespace) must be included in both search_text and
58
79
  indent_warning = ''
59
80
  if search_indent != replace_indent:
60
81
  indent_warning = f" [Warning: Indentation mismatch between search and replacement text: '{search_indent}' vs '{replace_indent}']"
82
+ if 'warning_detail' in locals():
83
+ return f"Text replaced in {file_path}{warning}{indent_warning}\n{warning_detail}"
61
84
  return f"Text replaced in {file_path}{warning}{indent_warning}"
62
85
 
63
86
  except Exception as e:
64
- print_error(f" ❌ Error: {e}")
87
+ self.report_error(" ❌ Error")
65
88
  return f"Error replacing text: {e}"
66
89
 
67
- ToolHandler.register_tool(ReplaceTextInFileTool, name="replace_text_in_file")
90
+ from janito.agent.tools.tools_utils import pluralize
@@ -1,80 +1,107 @@
1
- from janito.agent.tools.tool_base import ToolBase
2
- from janito.agent.tool_handler import ToolHandler
3
- import subprocess
4
-
5
- from janito.agent.tools.rich_utils import print_info, print_success, print_error
6
-
7
- import tempfile
8
- import os
9
- import sys
10
-
11
- class RunBashCommandTool(ToolBase):
12
- """
13
- Execute a non-interactive bash command and capture live output.
14
-
15
- Args:
16
- command (str): The bash command to execute.
17
- timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
18
- require_confirmation (bool, optional): If True, require user confirmation before running. Defaults to False.
19
- interactive (bool, optional): If True, warns that the command may require user interaction. Defaults to False. Non-interactive commands are preferred for automation and reliability.
20
-
21
- Returns:
22
- str: File paths and line counts for stdout and stderr.
23
- """
24
- def call(self, command: str, timeout: int = 60, require_confirmation: bool = False, interactive: bool = False) -> str:
25
- print_info(f"🖥️ Running bash command: {command}")
26
- if interactive:
27
- print_info("⚠️ Warning: This command might be interactive, require user input, and might hang.")
28
- print()
29
- sys.stdout.flush()
30
- self.update_progress(f"Running bash command: {command}")
31
- try:
32
- with tempfile.NamedTemporaryFile(mode='w+', prefix='run_bash_stdout_', delete=False, encoding='utf-8') as stdout_file, \
33
- tempfile.NamedTemporaryFile(mode='w+', prefix='run_bash_stderr_', delete=False, encoding='utf-8') as stderr_file:
34
- process = subprocess.Popen(
35
- command, shell=True,
36
- stdout=stdout_file,
37
- stderr=stderr_file,
38
- text=True
39
- )
40
- try:
41
- return_code = process.wait(timeout=timeout)
42
- except subprocess.TimeoutExpired:
43
- process.kill()
44
- print_error(f" Timed out after {timeout} seconds.")
45
- return f"Command timed out after {timeout} seconds."
46
-
47
- # Print live output to user
48
- stdout_file.flush()
49
- stderr_file.flush()
50
- with open(stdout_file.name, 'r', encoding='utf-8') as out_f:
51
- out_f.seek(0)
52
- for line in out_f:
53
- print(line, end='')
54
- with open(stderr_file.name, 'r', encoding='utf-8') as err_f:
55
- err_f.seek(0)
56
- for line in err_f:
57
- print(line, end='', file=sys.stderr)
58
-
59
- # Count lines
60
- with open(stdout_file.name, 'r', encoding='utf-8') as out_f:
61
- stdout_lines = sum(1 for _ in out_f)
62
- with open(stderr_file.name, 'r', encoding='utf-8') as err_f:
63
- stderr_lines = sum(1 for _ in err_f)
64
-
65
- print_success(f" ✅ return code {return_code}")
66
- warning_msg = ""
67
- if interactive:
68
- warning_msg = "⚠️ Warning: This command might be interactive, require user input, and might hang.\n"
69
- return (
70
- warning_msg +
71
- f"stdout_file: {stdout_file.name} (lines: {stdout_lines})\n"
72
- f"stderr_file: {stderr_file.name} (lines: {stderr_lines})\n"
73
- f"returncode: {return_code}\n"
74
- f"Use the get_lines tool to inspect the contents of these files when needed."
75
- )
76
- except Exception as e:
77
- print_error(f" ❌ Error: {e}")
78
- return f"Error running command: {e}"
79
-
80
- ToolHandler.register_tool(RunBashCommandTool, name="run_bash_command")
1
+ from janito.agent.tools.tool_base import ToolBase
2
+ from janito.agent.tool_registry import register_tool
3
+
4
+ import subprocess
5
+ import tempfile
6
+ import sys
7
+
8
+ @register_tool(name="run_bash_command")
9
+ class RunBashCommandTool(ToolBase):
10
+ """
11
+ Execute a non-interactive command using the bash shell and capture live output.
12
+
13
+ This tool explicitly invokes the 'bash' shell (not just the system default shell), so it requires bash to be installed and available in the system PATH. On Windows, this will only work if bash is available (e.g., via WSL, Git Bash, or similar).
14
+
15
+ Args:
16
+ command (str): The bash command to execute.
17
+ timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
18
+ require_confirmation (bool, optional): If True, require user confirmation before running. Defaults to False.
19
+ interactive (bool, optional): If True, warns that the command may require user interaction. Defaults to False. Non-interactive commands are preferred for automation and reliability.
20
+
21
+ Returns:
22
+ str: File paths and line counts for stdout and stderr.
23
+ """
24
+ def call(self, command: str, timeout: int = 60, require_confirmation: bool = False, interactive: bool = False) -> str:
25
+ """
26
+ Execute a bash command and capture live output.
27
+
28
+ Args:
29
+ command (str): The bash command to execute.
30
+ timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
31
+ require_confirmation (bool, optional): If True, require user confirmation before running. Defaults to False.
32
+ interactive (bool, optional): If True, warns that the command may require user interaction. Defaults to False.
33
+
34
+ Returns:
35
+ str: Output and status message.
36
+ """
37
+ """
38
+ Execute a bash command and capture live output.
39
+
40
+ Args:
41
+ command (str): The bash command to execute.
42
+ timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
43
+ require_confirmation (bool, optional): If True, require user confirmation before running. Defaults to False.
44
+ interactive (bool, optional): If True, warns that the command may require user interaction. Defaults to False.
45
+
46
+ Returns:
47
+ str: Output and status message.
48
+ """
49
+ if not command.strip():
50
+ self.report_warning("⚠️ Warning: Empty command provided. Operation skipped.")
51
+ return "Warning: Empty command provided. Operation skipped."
52
+ self.report_info(f"🖥️ Running bash command: {command}\n")
53
+ if interactive:
54
+ self.report_info("⚠️ Warning: This command might be interactive, require user input, and might hang.")
55
+
56
+ sys.stdout.flush()
57
+
58
+ try:
59
+ with tempfile.NamedTemporaryFile(mode='w+', prefix='run_bash_stdout_', delete=False, encoding='utf-8') as stdout_file, \
60
+ tempfile.NamedTemporaryFile(mode='w+', prefix='run_bash_stderr_', delete=False, encoding='utf-8') as stderr_file:
61
+ # Use bash explicitly for command execution
62
+ process = subprocess.Popen(
63
+ ["bash", "-c", command],
64
+ stdout=stdout_file,
65
+ stderr=stderr_file,
66
+ text=True
67
+ )
68
+ try:
69
+ return_code = process.wait(timeout=timeout)
70
+ except subprocess.TimeoutExpired:
71
+ process.kill()
72
+ self.report_error(f" Timed out after {timeout} seconds.")
73
+ return f"Command timed out after {timeout} seconds."
74
+
75
+ # Print live output to user
76
+ stdout_file.flush()
77
+ stderr_file.flush()
78
+ with open(stdout_file.name, 'r', encoding='utf-8') as out_f:
79
+ out_f.seek(0)
80
+ for line in out_f:
81
+ self.report_stdout(line)
82
+ with open(stderr_file.name, 'r', encoding='utf-8') as err_f:
83
+ err_f.seek(0)
84
+ for line in err_f:
85
+ self.report_stderr(line)
86
+
87
+ # Count lines
88
+ with open(stdout_file.name, 'r', encoding='utf-8') as out_f:
89
+ stdout_lines = sum(1 for _ in out_f)
90
+ with open(stderr_file.name, 'r', encoding='utf-8') as err_f:
91
+ stderr_lines = sum(1 for _ in err_f)
92
+
93
+ self.report_success(f" ✅ return code {return_code}")
94
+ warning_msg = ""
95
+ if interactive:
96
+ warning_msg = "⚠️ Warning: This command might be interactive, require user input, and might hang.\n"
97
+ return (
98
+ warning_msg +
99
+ f"stdout_file: {stdout_file.name} (lines: {stdout_lines})\n"
100
+ f"stderr_file: {stderr_file.name} (lines: {stderr_lines})\n"
101
+ f"returncode: {return_code}\n"
102
+ f"Use the get_lines tool to inspect the contents of these files when needed."
103
+ )
104
+ except Exception as e:
105
+ self.report_error(f" ❌ Error: {e}")
106
+ return f"Error running command: {e}"
107
+
@@ -1,26 +1,45 @@
1
1
  from janito.agent.tools.tool_base import ToolBase
2
- from janito.agent.tool_handler import ToolHandler
3
- import os
2
+ from janito.agent.tool_registry import register_tool
4
3
 
5
- from janito.agent.tools.rich_utils import print_info, print_success
4
+ import os
5
+ from janito.agent.tools.gitignore_utils import filter_ignored
6
6
 
7
+ @register_tool(name="search_files")
7
8
  class SearchFilesTool(ToolBase):
8
- """Search for a text pattern in all files within a directory and return matching lines."""
9
- def call(self, directory: str, pattern: str) -> str:
10
- print_info(f"🔎 Searching for pattern '{pattern}' in directory {directory}")
11
- self.update_progress(f"Searching for pattern '{pattern}' in directory {directory}")
9
+ """Search for a text pattern in all files within a directory and return matching lines. Respects .gitignore."""
10
+ def call(self, directories: list[str], pattern: str) -> str:
11
+ """
12
+ Search for a text pattern in all files within one or more directories and return matching lines.
13
+
14
+ Args:
15
+ directories (list[str]): List of directories to search in.
16
+ pattern (str): Plain text substring to search for in files. (Not a regular expression or glob pattern.)
17
+
18
+ Returns:
19
+ str: Matching lines from files as a newline-separated string, each formatted as 'filepath:lineno: line'. Example:
20
+ - "/path/to/file.py:10: def my_function():"
21
+ - "Warning: Empty search pattern provided. Operation skipped."
22
+ """
23
+ if not pattern:
24
+ self.report_warning("⚠️ Warning: Empty search pattern provided. Operation skipped.")
25
+ return "Warning: Empty search pattern provided. Operation skipped."
12
26
  matches = []
13
- for root, dirs, files in os.walk(directory):
14
- for filename in files:
15
- path = os.path.join(root, filename)
16
- try:
17
- with open(path, 'r', encoding='utf-8', errors='ignore') as f:
18
- for lineno, line in enumerate(f, 1):
19
- if pattern in line:
20
- matches.append(f"{path}:{lineno}: {line.strip()}")
21
- except Exception:
22
- continue
23
- print_success(f"\u2705 {len(matches)} matches found")
27
+ for directory in directories:
28
+ self.report_info(f"🔎 Searching for text '{pattern}' in '{directory}'")
29
+ for root, dirs, files in os.walk(directory):
30
+ dirs, files = filter_ignored(root, dirs, files)
31
+ for filename in files:
32
+ path = os.path.join(root, filename)
33
+ try:
34
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
35
+ for lineno, line in enumerate(f, 1):
36
+ if pattern in line:
37
+ matches.append(f"{path}:{lineno}: {line.strip()}")
38
+ except Exception:
39
+ continue
40
+
41
+ self.report_success(f" ✅ {len(matches)} {pluralize('line', len(matches))}")
24
42
  return '\n'.join(matches)
25
43
 
26
- ToolHandler.register_tool(SearchFilesTool, name="search_files")
44
+
45
+ from janito.agent.tools.tools_utils import pluralize
@@ -8,17 +8,44 @@ class ToolBase(ABC):
8
8
  self.progress_messages = []
9
9
  self._progress_callback = None # Will be set by ToolHandler if available
10
10
 
11
+ def report_stdout(self, message: str):
12
+ self.update_progress({"type": "stdout", "message": message})
13
+
14
+ def report_stderr(self, message: str):
15
+ self.update_progress({"type": "stderr", "message": message})
16
+
11
17
  @abstractmethod
12
18
  def call(self, **kwargs):
19
+ """
20
+ Abstract call method for tool execution. Should be overridden by subclasses.
21
+
22
+ Args:
23
+ **kwargs: Arbitrary keyword arguments for the tool.
24
+
25
+ Returns:
26
+ Any: The result of the tool execution.
27
+ """
13
28
  """
14
29
  Trigger the tool's action. Must be implemented by subclasses.
15
30
  """
16
31
  pass
17
32
 
18
- def update_progress(self, message: str):
33
+ def update_progress(self, progress: dict):
19
34
  """
20
35
  Report progress. Subclasses can override this to customize progress reporting.
21
36
  """
22
- self.progress_messages.append(message)
37
+ self.progress_messages.append(progress)
23
38
  if hasattr(self, '_progress_callback') and self._progress_callback:
24
- self._progress_callback({'event': 'progress', 'message': message})
39
+ self._progress_callback(progress)
40
+
41
+ def report_info(self, message: str):
42
+ self.update_progress({"type": "info", "tool": self.__class__.__name__, "message": message})
43
+
44
+ def report_success(self, message: str):
45
+ self.update_progress({"type": "success", "tool": self.__class__.__name__, "message": message})
46
+
47
+ def report_error(self, message: str):
48
+ self.update_progress({"type": "error", "tool": self.__class__.__name__, "message": message})
49
+
50
+ def report_warning(self, message: str):
51
+ self.update_progress({"type": "warning", "tool": self.__class__.__name__, "message": message})
@@ -0,0 +1,11 @@
1
+ def display_path(path):
2
+ import os
3
+ if os.path.isabs(path):
4
+ return path
5
+ return os.path.relpath(path)
6
+
7
+ def pluralize(word: str, count: int) -> str:
8
+ """Return the pluralized form of word if count != 1, unless word already ends with 's'."""
9
+ if count == 1 or word.endswith('s'):
10
+ return word
11
+ return word + 's'
@@ -1,5 +1,4 @@
1
1
  import os
2
- import sys
3
2
 
4
3
 
5
4
  def expand_path(path: str) -> str:
@@ -1,5 +1,5 @@
1
1
  import os
2
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, print_warning, print_magenta
2
+ from janito.rich_utils import print_info, print_warning, print_magenta
3
3
  from ._utils import home_shorten
4
4
 
5
5
  def print_config_items(items, color_label=None):
janito/cli/arg_parser.py CHANGED
@@ -12,7 +12,7 @@ def create_parser():
12
12
 
13
13
  # Mutually exclusive group for system prompt options
14
14
  group = parser.add_mutually_exclusive_group()
15
- group.add_argument("-s", "--system-prompt", type=str, default=None, help="Optional system prompt as a raw string.")
15
+ group.add_argument("-s", "--system", type=str, default=None, help="Optional system prompt as a raw string.")
16
16
  group.add_argument("--system-file", type=str, default=None, help="Path to a plain text file to use as the system prompt (no template rendering, takes precedence over --system-prompt)")
17
17
 
18
18
  parser.add_argument("-r", "--role", type=str, default=None, help="Role description for the default system prompt")
@@ -35,4 +35,5 @@ def create_parser():
35
35
  parser.add_argument("--config-reset-local", action="store_true", help="Remove the local config file (~/.janito/config.json)")
36
36
  parser.add_argument("--config-reset-global", action="store_true", help="Remove the global config file (~/.janito/config.json)")
37
37
  parser.add_argument("--trust", action="store_true", help="Enable trust mode: suppresses run_bash_command output, only shows output file locations.")
38
+ parser.add_argument("-V", "--vanilla", action="store_true", default=False, help="Vanilla mode: disables tools, system prompt, and temperature (unless -t is set)")
38
39
  return parser
@@ -1,6 +1,7 @@
1
1
  import sys
2
- from janito.agent.config import local_config, global_config
2
+ from janito.agent.config import local_config, global_config, CONFIG_OPTIONS
3
3
  from janito.agent.runtime_config import unified_config, runtime_config
4
+ from janito.agent.config_defaults import CONFIG_DEFAULTS
4
5
  from rich import print
5
6
  from ._utils import home_shorten
6
7
 
@@ -22,7 +23,6 @@ def handle_config_commands(args):
22
23
  sys.exit(1)
23
24
  runtime_config.set(key, val.strip())
24
25
  if args.set_local_config:
25
- from janito.agent.config import CONFIG_OPTIONS
26
26
  try:
27
27
  key, val = args.set_local_config.split("=", 1)
28
28
  except ValueError:
@@ -39,7 +39,6 @@ def handle_config_commands(args):
39
39
  did_something = True
40
40
 
41
41
  if args.set_global_config:
42
- from janito.agent.config import CONFIG_OPTIONS
43
42
  try:
44
43
  key, val = args.set_global_config.split("=", 1)
45
44
  except ValueError:
@@ -83,15 +82,13 @@ def handle_config_commands(args):
83
82
  global_items = {}
84
83
 
85
84
  # Collect and group keys
86
- from janito.agent.config_defaults import CONFIG_DEFAULTS
87
85
  local_keys = set(local_config.all().keys())
88
86
  global_keys = set(global_config.all().keys())
89
87
  all_keys = set(CONFIG_DEFAULTS.keys()) | global_keys | local_keys
90
88
  if not (local_keys or global_keys):
91
89
  print("No configuration found.")
92
90
  else:
93
- from janito.agent.config import get_api_key
94
- from janito.agent.runtime_config import unified_config
91
+ # Imports previously inside block to avoid circular import at module level
95
92
  # Handle template as nested dict
96
93
  for key in sorted(local_keys):
97
94
  if key == "template":
janito/cli/main.py CHANGED
@@ -20,7 +20,7 @@ def main():
20
20
  parser = create_parser()
21
21
  args = parser.parse_args()
22
22
 
23
- from janito.agent.config import CONFIG_OPTIONS
23
+ from janito.agent.config import CONFIG_OPTIONS # Kept here: avoids circular import at module level
24
24
  from janito.agent.config_defaults import CONFIG_DEFAULTS
25
25
  import sys
26
26
  if getattr(args, "help_config", False):
@@ -33,7 +33,7 @@ def main():
33
33
  handle_config_commands(args)
34
34
  setup_verbose_logging(args)
35
35
  if getattr(args, 'web', False):
36
- import subprocess
36
+ import subprocess # Only needed if launching web
37
37
  subprocess.run(['python', '-m', 'janito.web'])
38
38
  else:
39
39
  run_cli(args)