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.
- janito/__init__.py +1 -1
- janito/agent/__init__.py +0 -1
- janito/agent/agent.py +7 -25
- janito/agent/config.py +4 -6
- janito/agent/config_defaults.py +2 -2
- janito/agent/content_handler.py +0 -0
- janito/agent/conversation.py +63 -37
- janito/agent/message_handler.py +18 -0
- janito/agent/openai_schema_generator.py +116 -0
- janito/agent/queued_message_handler.py +32 -0
- janito/agent/rich_tool_handler.py +43 -0
- janito/agent/runtime_config.py +1 -1
- janito/agent/templates/system_instructions.j2 +10 -4
- janito/agent/tool_registry.py +92 -0
- janito/agent/tools/append_text_to_file.py +41 -0
- janito/agent/tools/ask_user.py +16 -3
- janito/agent/tools/create_directory.py +31 -0
- janito/agent/tools/create_file.py +52 -0
- janito/agent/tools/fetch_url.py +23 -8
- janito/agent/tools/find_files.py +40 -21
- janito/agent/tools/get_file_outline.py +26 -8
- janito/agent/tools/get_lines.py +53 -19
- janito/agent/tools/move_file.py +50 -0
- janito/agent/tools/py_compile.py +27 -11
- janito/agent/tools/python_exec.py +43 -14
- janito/agent/tools/remove_directory.py +23 -7
- janito/agent/tools/remove_file.py +38 -0
- janito/agent/tools/replace_text_in_file.py +40 -17
- janito/agent/tools/run_bash_command.py +107 -80
- janito/agent/tools/search_files.py +38 -19
- janito/agent/tools/tool_base.py +30 -3
- janito/agent/tools/tools_utils.py +11 -0
- janito/agent/tools/utils.py +0 -1
- janito/cli/_print_config.py +1 -1
- janito/cli/arg_parser.py +2 -1
- janito/cli/config_commands.py +3 -6
- janito/cli/main.py +2 -2
- janito/cli/runner.py +18 -14
- janito/cli_chat_shell/chat_loop.py +10 -15
- janito/cli_chat_shell/commands.py +8 -3
- janito/cli_chat_shell/config_shell.py +0 -3
- janito/cli_chat_shell/session_manager.py +11 -0
- janito/cli_chat_shell/ui.py +12 -113
- janito/render_prompt.py +0 -1
- janito/rich_utils.py +30 -0
- janito/web/app.py +10 -12
- janito-1.5.0.dist-info/METADATA +176 -0
- janito-1.5.0.dist-info/RECORD +64 -0
- janito/agent/queued_tool_handler.py +0 -16
- janito/agent/tool_handler.py +0 -196
- janito/agent/tools/file_ops.py +0 -114
- janito/agent/tools/rich_utils.py +0 -31
- janito-1.4.0.dist-info/METADATA +0 -142
- janito-1.4.0.dist-info/RECORD +0 -55
- {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/WHEEL +0 -0
- {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/entry_points.txt +0 -0
- {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {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.
|
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
|
-
|
12
|
-
|
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
|
-
|
32
|
+
|
33
|
+
self.report_success(f"✅ 1 {pluralize('directory', 1)}")
|
19
34
|
return f"Directory removed: {directory}"
|
20
35
|
except Exception as e:
|
21
|
-
|
36
|
+
self.report_error(f" ❌ Error removing directory: {e}")
|
22
37
|
return f"Error removing directory: {e}"
|
23
38
|
|
24
|
-
|
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.
|
3
|
-
|
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
|
23
|
-
|
24
|
-
action = "all occurrences" if replace_all else
|
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
|
-
|
29
|
-
|
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
|
-
|
41
|
-
|
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
|
-
|
45
|
-
|
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 =
|
49
|
-
|
50
|
-
|
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
|
-
|
87
|
+
self.report_error(" ❌ Error")
|
65
88
|
return f"Error replacing text: {e}"
|
66
89
|
|
67
|
-
|
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.
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
f"
|
73
|
-
f"
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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.
|
3
|
-
import os
|
2
|
+
from janito.agent.tool_registry import register_tool
|
4
3
|
|
5
|
-
|
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,
|
10
|
-
|
11
|
-
|
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
|
14
|
-
for
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
44
|
+
|
45
|
+
from janito.agent.tools.tools_utils import pluralize
|
janito/agent/tools/tool_base.py
CHANGED
@@ -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,
|
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(
|
37
|
+
self.progress_messages.append(progress)
|
23
38
|
if hasattr(self, '_progress_callback') and self._progress_callback:
|
24
|
-
self._progress_callback(
|
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'
|
janito/agent/tools/utils.py
CHANGED
janito/cli/_print_config.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import os
|
2
|
-
from janito.
|
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
|
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
|
janito/cli/config_commands.py
CHANGED
@@ -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
|
-
|
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)
|