janito 1.8.1__py3-none-any.whl → 1.10.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/api_exceptions.py +4 -0
- janito/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -3
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +177 -114
- janito/agent/conversation_api.py +179 -159
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/llm_conversation_history.py +70 -0
- janito/agent/openai_client.py +44 -21
- janito/agent/openai_schema_generator.py +164 -128
- janito/agent/platform_discovery.py +134 -77
- janito/agent/profile_manager.py +5 -5
- janito/agent/rich_message_handler.py +80 -31
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +9 -8
- janito/agent/test_openai_schema_generator.py +93 -0
- janito/agent/tool_base.py +7 -2
- janito/agent/tool_executor.py +63 -50
- janito/agent/tool_registry.py +5 -2
- janito/agent/tool_use_tracker.py +42 -5
- janito/agent/tools/__init__.py +13 -12
- janito/agent/tools/create_directory.py +9 -6
- janito/agent/tools/create_file.py +35 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +50 -5
- janito/agent/tools/find_files.py +40 -26
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +14 -18
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +11 -0
- janito/agent/tools/get_lines.py +21 -12
- janito/agent/tools/move_file.py +13 -12
- janito/agent/tools/present_choices.py +3 -1
- janito/agent/tools/python_command_runner.py +150 -0
- janito/agent/tools/python_file_runner.py +148 -0
- janito/agent/tools/python_stdin_runner.py +154 -0
- janito/agent/tools/remove_directory.py +4 -2
- janito/agent/tools/remove_file.py +15 -13
- janito/agent/tools/replace_file.py +72 -0
- janito/agent/tools/replace_text_in_file.py +7 -5
- janito/agent/tools/run_bash_command.py +29 -72
- janito/agent/tools/run_powershell_command.py +142 -102
- janito/agent/tools/search_text.py +177 -131
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +94 -0
- janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
- janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
- janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
- janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
- janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
- janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
- janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/agent/tools_utils/__init__.py +1 -0
- janito/agent/tools_utils/action_type.py +7 -0
- janito/agent/tools_utils/dir_walk_utils.py +24 -0
- janito/agent/tools_utils/formatting.py +49 -0
- janito/agent/tools_utils/gitignore_utils.py +69 -0
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +57 -14
- janito/cli/cli_main.py +270 -0
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +166 -99
- janito/cli/one_shot.py +80 -0
- janito/cli/termweb_starter.py +2 -2
- janito/i18n/__init__.py +1 -1
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +41 -25
- janito/{cli_chat_shell → shell}/commands/__init__.py +19 -14
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +74 -0
- janito/shell/commands/edit.py +24 -0
- janito/shell/commands/history_view.py +18 -0
- janito/{cli_chat_shell → shell}/commands/lang.py +3 -0
- janito/shell/commands/livelogs.py +42 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +16 -6
- janito/shell/commands/session.py +35 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +3 -5
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +18 -10
- janito/shell/commands/tools.py +26 -0
- janito/shell/commands/track.py +36 -0
- janito/shell/commands/utility.py +28 -0
- janito/{cli_chat_shell → shell}/commands/verbose.py +4 -5
- janito/shell/commands.py +40 -0
- janito/shell/input_history.py +62 -0
- janito/shell/main.py +257 -0
- janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
- janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
- janito/shell/session/manager.py +101 -0
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -17
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +142 -0
- janito/termweb/static/editor.css.bak +27 -0
- janito/termweb/static/editor.html +15 -213
- janito/termweb/static/editor.html.bak +16 -215
- janito/termweb/static/editor.js +209 -0
- janito/termweb/static/editor.js.bak +227 -0
- janito/termweb/static/index.html +2 -3
- janito/termweb/static/index.html.bak +2 -3
- janito/termweb/static/termweb.css.bak +33 -84
- janito/termweb/static/termweb.js +15 -34
- janito/termweb/static/termweb.js.bak +18 -36
- janito/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/METADATA +62 -42
- janito-1.10.0.dist-info/RECORD +158 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/gitignore_utils.py +0 -46
- janito/agent/tools/memory.py +0 -48
- janito/agent/tools/outline_file/formatting.py +0 -20
- janito/agent/tools/outline_file/python_outline.py +0 -71
- janito/agent/tools/present_choices_test.py +0 -18
- janito/agent/tools/rich_live.py +0 -44
- janito/agent/tools/run_python_command.py +0 -163
- janito/agent/tools/tools_utils.py +0 -56
- janito/agent/tools/utils.py +0 -33
- janito/agent/tools/validate_file_syntax.py +0 -163
- janito/cli/runner/cli_main.py +0 -180
- janito/cli_chat_shell/chat_loop.py +0 -163
- janito/cli_chat_shell/chat_state.py +0 -38
- janito/cli_chat_shell/commands/history_start.py +0 -37
- janito/cli_chat_shell/commands/session.py +0 -48
- janito/cli_chat_shell/commands/sum.py +0 -49
- janito/cli_chat_shell/commands/utility.py +0 -32
- janito/cli_chat_shell/session_manager.py +0 -72
- janito-1.8.1.dist-info/RECORD +0 -127
- /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
- /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
- /janito/cli/{runner/config.py → config_runner.py} +0 -0
- /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
- /janito/{cli/runner → shell}/__init__.py +0 -0
- /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
- /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
- /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/top_level.txt +0 -0
janito/agent/tools/get_lines.py
CHANGED
@@ -1,40 +1,49 @@
|
|
1
1
|
from janito.agent.tool_base import ToolBase
|
2
|
+
from janito.agent.tools_utils.action_type import ActionType
|
2
3
|
from janito.agent.tool_registry import register_tool
|
3
|
-
from janito.agent.
|
4
|
+
from janito.agent.tools_utils.utils import pluralize
|
4
5
|
from janito.i18n import tr
|
5
6
|
|
6
7
|
|
7
8
|
@register_tool(name="get_lines")
|
8
9
|
class GetLinesTool(ToolBase):
|
9
10
|
"""
|
10
|
-
Read lines from a file.
|
11
|
+
Read lines from a file. You can specify a line range, or read the entire file by simply omitting the from_line and to_line parameters.
|
12
|
+
|
11
13
|
Args:
|
12
14
|
file_path (str): Path to the file to read lines from.
|
13
|
-
from_line (int, optional): Starting line number (1-based).
|
14
|
-
to_line (int, optional): Ending line number (1-based).
|
15
|
+
from_line (int, optional): Starting line number (1-based). Omit to start from the first line.
|
16
|
+
to_line (int, optional): Ending line number (1-based). Omit to read to the end of the file.
|
17
|
+
|
18
|
+
To read the full file, just provide file_path and leave from_line and to_line unset.
|
19
|
+
|
15
20
|
Returns:
|
16
21
|
str: File content with a header indicating the file name and line range. Example:
|
17
22
|
- "---\nFile: /path/to/file.py | Lines: 1-10 (of 100)\n---\n<lines...>"
|
18
|
-
- "---\nFile: /path/to/file.py | All lines (total: 100)\n---\n<all lines...>"
|
23
|
+
- "---\nFile: /path/to/file.py | All lines (total: 100 (all))\n---\n<all lines...>"
|
19
24
|
- "Error reading file: <error message>"
|
20
25
|
- "❗ not found"
|
21
26
|
"""
|
22
27
|
|
23
28
|
def run(self, file_path: str, from_line: int = None, to_line: int = None) -> str:
|
24
|
-
from janito.agent.
|
29
|
+
from janito.agent.tools_utils.utils import display_path
|
25
30
|
|
26
31
|
disp_path = display_path(file_path)
|
27
32
|
if from_line and to_line:
|
28
33
|
self.report_info(
|
34
|
+
ActionType.READ,
|
29
35
|
tr(
|
30
|
-
"📖 Reading {disp_path} {from_line}-{to_line}",
|
36
|
+
"📖 Reading file '{disp_path}' {from_line}-{to_line}",
|
31
37
|
disp_path=disp_path,
|
32
38
|
from_line=from_line,
|
33
39
|
to_line=to_line,
|
34
|
-
)
|
40
|
+
),
|
35
41
|
)
|
36
42
|
else:
|
37
|
-
self.report_info(
|
43
|
+
self.report_info(
|
44
|
+
ActionType.READ,
|
45
|
+
tr("📖 Reading file '{disp_path}'", disp_path=disp_path),
|
46
|
+
)
|
38
47
|
try:
|
39
48
|
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
40
49
|
lines = f.readlines()
|
@@ -59,7 +68,7 @@ class GetLinesTool(ToolBase):
|
|
59
68
|
elif to_line < total_lines:
|
60
69
|
self.report_success(
|
61
70
|
tr(
|
62
|
-
" ✅ {selected_len} {line_word} ({remaining} to
|
71
|
+
" ✅ {selected_len} {line_word} ({remaining} to end)",
|
63
72
|
selected_len=selected_len,
|
64
73
|
line_word=pluralize("line", selected_len),
|
65
74
|
remaining=total_lines - to_line,
|
@@ -68,7 +77,7 @@ class GetLinesTool(ToolBase):
|
|
68
77
|
else:
|
69
78
|
self.report_success(
|
70
79
|
tr(
|
71
|
-
" ✅ {selected_len} {line_word}",
|
80
|
+
" ✅ {selected_len} {line_word} (all)",
|
72
81
|
selected_len=selected_len,
|
73
82
|
line_word=pluralize("line", selected_len),
|
74
83
|
)
|
@@ -98,7 +107,7 @@ class GetLinesTool(ToolBase):
|
|
98
107
|
)
|
99
108
|
else:
|
100
109
|
header = tr(
|
101
|
-
"---\n{disp_path} All lines (total: {total_lines})\n---\n",
|
110
|
+
"---\n{disp_path} All lines (total: {total_lines} (all))\n---\n",
|
102
111
|
disp_path=disp_path,
|
103
112
|
total_lines=total_lines,
|
104
113
|
)
|
janito/agent/tools/move_file.py
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
import os
|
2
2
|
import shutil
|
3
3
|
from janito.agent.tool_registry import register_tool
|
4
|
-
|
4
|
+
|
5
|
+
# from janito.agent.tools_utils.expand_path import expand_path
|
6
|
+
from janito.agent.tools_utils.utils import display_path
|
5
7
|
from janito.agent.tool_base import ToolBase
|
8
|
+
from janito.agent.tools_utils.action_type import ActionType
|
6
9
|
from janito.i18n import tr
|
7
10
|
|
8
11
|
|
@@ -29,8 +32,8 @@ class MoveFileTool(ToolBase):
|
|
29
32
|
) -> str:
|
30
33
|
original_src = src_path
|
31
34
|
original_dest = dest_path
|
32
|
-
src =
|
33
|
-
dest =
|
35
|
+
src = src_path # Using src_path as is
|
36
|
+
dest = dest_path # Using dest_path as is
|
34
37
|
disp_src = display_path(original_src)
|
35
38
|
disp_dest = display_path(original_dest)
|
36
39
|
backup_path = None
|
@@ -82,19 +85,17 @@ class MoveFileTool(ToolBase):
|
|
82
85
|
)
|
83
86
|
return tr("❌ Error removing destination before move: {error}", error=e)
|
84
87
|
try:
|
85
|
-
|
86
|
-
|
88
|
+
self.report_info(
|
89
|
+
ActionType.WRITE,
|
87
90
|
tr(
|
88
|
-
"
|
91
|
+
"📝 Moving from '{disp_src}' to '{disp_dest}' ...",
|
89
92
|
disp_src=disp_src,
|
90
93
|
disp_dest=disp_dest,
|
91
|
-
)
|
92
|
-
)
|
93
|
-
msg = tr(
|
94
|
-
"✅ Successfully moved from '{disp_src}' to '{disp_dest}'.",
|
95
|
-
disp_src=disp_src,
|
96
|
-
disp_dest=disp_dest,
|
94
|
+
),
|
97
95
|
)
|
96
|
+
shutil.move(src, dest)
|
97
|
+
self.report_success(tr("✅ Move complete."))
|
98
|
+
msg = tr("✅ Move complete.")
|
98
99
|
if backup_path:
|
99
100
|
msg += tr(
|
100
101
|
" (backup at {backup_disp})",
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from typing import List
|
2
2
|
from janito.agent.tool_base import ToolBase
|
3
|
+
from janito.agent.tools_utils.action_type import ActionType
|
3
4
|
from janito.agent.tool_registry import register_tool
|
4
5
|
from janito.i18n import tr
|
5
6
|
import questionary
|
@@ -36,11 +37,12 @@ class PresentChoicesTool(ToolBase):
|
|
36
37
|
if not choices:
|
37
38
|
return tr("⚠️ No choices provided.")
|
38
39
|
self.report_info(
|
40
|
+
ActionType.EXECUTE,
|
39
41
|
tr(
|
40
42
|
"ℹ️ Prompting user: {prompt} (multi_select={multi_select}) ...",
|
41
43
|
prompt=prompt,
|
42
44
|
multi_select=multi_select,
|
43
|
-
)
|
45
|
+
),
|
44
46
|
)
|
45
47
|
if multi_select:
|
46
48
|
result = questionary.checkbox(
|
@@ -0,0 +1,150 @@
|
|
1
|
+
import subprocess
|
2
|
+
import sys
|
3
|
+
import tempfile
|
4
|
+
import threading
|
5
|
+
from janito.agent.tool_base import ToolBase
|
6
|
+
from janito.agent.tools_utils.action_type import ActionType
|
7
|
+
from janito.agent.tool_registry import register_tool
|
8
|
+
from janito.i18n import tr
|
9
|
+
|
10
|
+
|
11
|
+
@register_tool(name="python_command_runner")
|
12
|
+
class PythonCommandRunnerTool(ToolBase):
|
13
|
+
"""
|
14
|
+
Tool to execute Python code using the `python -c` command-line flag.
|
15
|
+
Args:
|
16
|
+
code (str): The Python code to execute as a string.
|
17
|
+
timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
|
18
|
+
Returns:
|
19
|
+
str: Output and status message, or file paths/line counts if output is large.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def run(self, code: str, timeout: int = 60) -> str:
|
23
|
+
if not code.strip():
|
24
|
+
self.report_warning(tr("\u2139\ufe0f Empty code provided."))
|
25
|
+
return tr("Warning: Empty code provided. Operation skipped.")
|
26
|
+
self.report_info(
|
27
|
+
ActionType.EXECUTE, tr("🐍 Running: python -c ...\n{code}\n", code=code)
|
28
|
+
)
|
29
|
+
try:
|
30
|
+
with (
|
31
|
+
tempfile.NamedTemporaryFile(
|
32
|
+
mode="w+",
|
33
|
+
prefix="python_cmd_stdout_",
|
34
|
+
delete=False,
|
35
|
+
encoding="utf-8",
|
36
|
+
) as stdout_file,
|
37
|
+
tempfile.NamedTemporaryFile(
|
38
|
+
mode="w+",
|
39
|
+
prefix="python_cmd_stderr_",
|
40
|
+
delete=False,
|
41
|
+
encoding="utf-8",
|
42
|
+
) as stderr_file,
|
43
|
+
):
|
44
|
+
process = subprocess.Popen(
|
45
|
+
[sys.executable, "-c", code],
|
46
|
+
stdout=subprocess.PIPE,
|
47
|
+
stderr=subprocess.PIPE,
|
48
|
+
text=True,
|
49
|
+
bufsize=1,
|
50
|
+
universal_newlines=True,
|
51
|
+
encoding="utf-8",
|
52
|
+
env={**dict(), **dict(PYTHONIOENCODING="utf-8")},
|
53
|
+
)
|
54
|
+
stdout_lines = 0
|
55
|
+
stderr_lines = 0
|
56
|
+
|
57
|
+
def stream_output(stream, file_obj, report_func, count_func):
|
58
|
+
nonlocal stdout_lines, stderr_lines
|
59
|
+
for line in stream:
|
60
|
+
file_obj.write(line)
|
61
|
+
file_obj.flush()
|
62
|
+
report_func(line)
|
63
|
+
if count_func == "stdout":
|
64
|
+
stdout_lines += 1
|
65
|
+
else:
|
66
|
+
stderr_lines += 1
|
67
|
+
|
68
|
+
stdout_thread = threading.Thread(
|
69
|
+
target=stream_output,
|
70
|
+
args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
|
71
|
+
)
|
72
|
+
stderr_thread = threading.Thread(
|
73
|
+
target=stream_output,
|
74
|
+
args=(process.stderr, stderr_file, self.report_stderr, "stderr"),
|
75
|
+
)
|
76
|
+
stdout_thread.start()
|
77
|
+
stderr_thread.start()
|
78
|
+
try:
|
79
|
+
return_code = process.wait(timeout=timeout)
|
80
|
+
except subprocess.TimeoutExpired:
|
81
|
+
process.kill()
|
82
|
+
self.report_error(
|
83
|
+
tr("\u274c Timed out after {timeout} seconds.", timeout=timeout)
|
84
|
+
)
|
85
|
+
return tr(
|
86
|
+
"Code timed out after {timeout} seconds.", timeout=timeout
|
87
|
+
)
|
88
|
+
stdout_thread.join()
|
89
|
+
stderr_thread.join()
|
90
|
+
stdout_file.flush()
|
91
|
+
stderr_file.flush()
|
92
|
+
|
93
|
+
self.report_success(
|
94
|
+
tr("\u2705 Return code {return_code}", return_code=return_code)
|
95
|
+
)
|
96
|
+
# Read back the content for summary if not too large
|
97
|
+
with open(
|
98
|
+
stdout_file.name, "r", encoding="utf-8", errors="replace"
|
99
|
+
) as out_f:
|
100
|
+
stdout_content = out_f.read()
|
101
|
+
with open(
|
102
|
+
stderr_file.name, "r", encoding="utf-8", errors="replace"
|
103
|
+
) as err_f:
|
104
|
+
stderr_content = err_f.read()
|
105
|
+
max_lines = 100
|
106
|
+
stdout_lines = stdout_content.count("\n")
|
107
|
+
stderr_lines = stderr_content.count("\n")
|
108
|
+
|
109
|
+
def head_tail(text, n=10):
|
110
|
+
lines = text.splitlines()
|
111
|
+
if len(lines) <= 2 * n:
|
112
|
+
return "\n".join(lines)
|
113
|
+
return "\n".join(
|
114
|
+
lines[:n]
|
115
|
+
+ ["... ({} lines omitted) ...".format(len(lines) - 2 * n)]
|
116
|
+
+ lines[-n:]
|
117
|
+
)
|
118
|
+
|
119
|
+
if stdout_lines <= max_lines and stderr_lines <= max_lines:
|
120
|
+
result = (
|
121
|
+
f"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}"
|
122
|
+
)
|
123
|
+
if stderr_content.strip():
|
124
|
+
result += f"\n--- STDERR ---\n{stderr_content}"
|
125
|
+
return result
|
126
|
+
else:
|
127
|
+
result = (
|
128
|
+
f"stdout_file: {stdout_file.name} (lines: {stdout_lines})\n"
|
129
|
+
)
|
130
|
+
if stderr_lines > 0 and stderr_content.strip():
|
131
|
+
result += (
|
132
|
+
f"stderr_file: {stderr_file.name} (lines: {stderr_lines})\n"
|
133
|
+
)
|
134
|
+
result += f"returncode: {return_code}\n"
|
135
|
+
result += (
|
136
|
+
"--- STDOUT (head/tail) ---\n"
|
137
|
+
+ head_tail(stdout_content)
|
138
|
+
+ "\n"
|
139
|
+
)
|
140
|
+
if stderr_content.strip():
|
141
|
+
result += (
|
142
|
+
"--- STDERR (head/tail) ---\n"
|
143
|
+
+ head_tail(stderr_content)
|
144
|
+
+ "\n"
|
145
|
+
)
|
146
|
+
result += "Use the get_lines tool to inspect the contents of these files when needed."
|
147
|
+
return result
|
148
|
+
except Exception as e:
|
149
|
+
self.report_error(tr("\u274c Error: {error}", error=e))
|
150
|
+
return tr("Error running code: {error}", error=e)
|
@@ -0,0 +1,148 @@
|
|
1
|
+
import subprocess
|
2
|
+
import sys
|
3
|
+
import tempfile
|
4
|
+
import threading
|
5
|
+
from janito.agent.tool_base import ToolBase
|
6
|
+
from janito.agent.tools_utils.action_type import ActionType
|
7
|
+
from janito.agent.tool_registry import register_tool
|
8
|
+
from janito.i18n import tr
|
9
|
+
|
10
|
+
|
11
|
+
@register_tool(name="python_file_runner")
|
12
|
+
class PythonFileRunnerTool(ToolBase):
|
13
|
+
"""
|
14
|
+
Tool to execute a specified Python script file.
|
15
|
+
Args:
|
16
|
+
file_path (str): Path to the Python script file to execute.
|
17
|
+
timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
|
18
|
+
Returns:
|
19
|
+
str: Output and status message, or file paths/line counts if output is large.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def run(self, file_path: str, timeout: int = 60) -> str:
|
23
|
+
self.report_info(
|
24
|
+
ActionType.EXECUTE,
|
25
|
+
tr("🚀 Running: python {file_path}", file_path=file_path),
|
26
|
+
)
|
27
|
+
try:
|
28
|
+
with (
|
29
|
+
tempfile.NamedTemporaryFile(
|
30
|
+
mode="w+",
|
31
|
+
prefix="python_file_stdout_",
|
32
|
+
delete=False,
|
33
|
+
encoding="utf-8",
|
34
|
+
) as stdout_file,
|
35
|
+
tempfile.NamedTemporaryFile(
|
36
|
+
mode="w+",
|
37
|
+
prefix="python_file_stderr_",
|
38
|
+
delete=False,
|
39
|
+
encoding="utf-8",
|
40
|
+
) as stderr_file,
|
41
|
+
):
|
42
|
+
process = subprocess.Popen(
|
43
|
+
[sys.executable, file_path],
|
44
|
+
stdout=subprocess.PIPE,
|
45
|
+
stderr=subprocess.PIPE,
|
46
|
+
text=True,
|
47
|
+
bufsize=1,
|
48
|
+
universal_newlines=True,
|
49
|
+
encoding="utf-8",
|
50
|
+
env={**dict(), **dict(PYTHONIOENCODING="utf-8")},
|
51
|
+
)
|
52
|
+
stdout_lines = 0
|
53
|
+
stderr_lines = 0
|
54
|
+
|
55
|
+
def stream_output(stream, file_obj, report_func, count_func):
|
56
|
+
nonlocal stdout_lines, stderr_lines
|
57
|
+
for line in stream:
|
58
|
+
file_obj.write(line)
|
59
|
+
file_obj.flush()
|
60
|
+
report_func(line)
|
61
|
+
if count_func == "stdout":
|
62
|
+
stdout_lines += 1
|
63
|
+
else:
|
64
|
+
stderr_lines += 1
|
65
|
+
|
66
|
+
stdout_thread = threading.Thread(
|
67
|
+
target=stream_output,
|
68
|
+
args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
|
69
|
+
)
|
70
|
+
stderr_thread = threading.Thread(
|
71
|
+
target=stream_output,
|
72
|
+
args=(process.stderr, stderr_file, self.report_stderr, "stderr"),
|
73
|
+
)
|
74
|
+
stdout_thread.start()
|
75
|
+
stderr_thread.start()
|
76
|
+
try:
|
77
|
+
return_code = process.wait(timeout=timeout)
|
78
|
+
except subprocess.TimeoutExpired:
|
79
|
+
process.kill()
|
80
|
+
self.report_error(
|
81
|
+
tr("\u274c Timed out after {timeout} seconds.", timeout=timeout)
|
82
|
+
)
|
83
|
+
return tr(
|
84
|
+
"Code timed out after {timeout} seconds.", timeout=timeout
|
85
|
+
)
|
86
|
+
stdout_thread.join()
|
87
|
+
stderr_thread.join()
|
88
|
+
stdout_file.flush()
|
89
|
+
stderr_file.flush()
|
90
|
+
|
91
|
+
self.report_success(
|
92
|
+
tr("\u2705 Return code {return_code}", return_code=return_code)
|
93
|
+
)
|
94
|
+
# Read back the content for summary if not too large
|
95
|
+
with open(
|
96
|
+
stdout_file.name, "r", encoding="utf-8", errors="replace"
|
97
|
+
) as out_f:
|
98
|
+
stdout_content = out_f.read()
|
99
|
+
with open(
|
100
|
+
stderr_file.name, "r", encoding="utf-8", errors="replace"
|
101
|
+
) as err_f:
|
102
|
+
stderr_content = err_f.read()
|
103
|
+
max_lines = 100
|
104
|
+
stdout_lines = stdout_content.count("\n")
|
105
|
+
stderr_lines = stderr_content.count("\n")
|
106
|
+
|
107
|
+
def head_tail(text, n=10):
|
108
|
+
lines = text.splitlines()
|
109
|
+
if len(lines) <= 2 * n:
|
110
|
+
return "\n".join(lines)
|
111
|
+
return "\n".join(
|
112
|
+
lines[:n]
|
113
|
+
+ ["... ({} lines omitted) ...".format(len(lines) - 2 * n)]
|
114
|
+
+ lines[-n:]
|
115
|
+
)
|
116
|
+
|
117
|
+
if stdout_lines <= max_lines and stderr_lines <= max_lines:
|
118
|
+
result = (
|
119
|
+
f"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}"
|
120
|
+
)
|
121
|
+
if stderr_content.strip():
|
122
|
+
result += f"\n--- STDERR ---\n{stderr_content}"
|
123
|
+
return result
|
124
|
+
else:
|
125
|
+
result = (
|
126
|
+
f"stdout_file: {stdout_file.name} (lines: {stdout_lines})\n"
|
127
|
+
)
|
128
|
+
if stderr_lines > 0 and stderr_content.strip():
|
129
|
+
result += (
|
130
|
+
f"stderr_file: {stderr_file.name} (lines: {stderr_lines})\n"
|
131
|
+
)
|
132
|
+
result += f"returncode: {return_code}\n"
|
133
|
+
result += (
|
134
|
+
"--- STDOUT (head/tail) ---\n"
|
135
|
+
+ head_tail(stdout_content)
|
136
|
+
+ "\n"
|
137
|
+
)
|
138
|
+
if stderr_content.strip():
|
139
|
+
result += (
|
140
|
+
"--- STDERR (head/tail) ---\n"
|
141
|
+
+ head_tail(stderr_content)
|
142
|
+
+ "\n"
|
143
|
+
)
|
144
|
+
result += "Use the get_lines tool to inspect the contents of these files when needed."
|
145
|
+
return result
|
146
|
+
except Exception as e:
|
147
|
+
self.report_error(tr("\u274c Error: {error}", error=e))
|
148
|
+
return tr("Error running file: {error}", error=e)
|
@@ -0,0 +1,154 @@
|
|
1
|
+
import subprocess
|
2
|
+
import sys
|
3
|
+
import tempfile
|
4
|
+
import threading
|
5
|
+
from janito.agent.tool_base import ToolBase
|
6
|
+
from janito.agent.tools_utils.action_type import ActionType
|
7
|
+
from janito.agent.tool_registry import register_tool
|
8
|
+
from janito.i18n import tr
|
9
|
+
|
10
|
+
|
11
|
+
@register_tool(name="python_stdin_runner")
|
12
|
+
class PythonStdinRunnerTool(ToolBase):
|
13
|
+
"""
|
14
|
+
Tool to execute Python code by passing it to the interpreter via standard input (stdin).
|
15
|
+
Args:
|
16
|
+
code (str): The Python code to execute as a string.
|
17
|
+
timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
|
18
|
+
Returns:
|
19
|
+
str: Output and status message, or file paths/line counts if output is large.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def run(self, code: str, timeout: int = 60) -> str:
|
23
|
+
if not code.strip():
|
24
|
+
self.report_warning(tr("\u2139\ufe0f Empty code provided."))
|
25
|
+
return tr("Warning: Empty code provided. Operation skipped.")
|
26
|
+
self.report_info(
|
27
|
+
ActionType.EXECUTE,
|
28
|
+
tr("⚡ Running: python (stdin mode) ...\n{code}\n", code=code),
|
29
|
+
)
|
30
|
+
try:
|
31
|
+
with (
|
32
|
+
tempfile.NamedTemporaryFile(
|
33
|
+
mode="w+",
|
34
|
+
prefix="python_stdin_stdout_",
|
35
|
+
delete=False,
|
36
|
+
encoding="utf-8",
|
37
|
+
) as stdout_file,
|
38
|
+
tempfile.NamedTemporaryFile(
|
39
|
+
mode="w+",
|
40
|
+
prefix="python_stdin_stderr_",
|
41
|
+
delete=False,
|
42
|
+
encoding="utf-8",
|
43
|
+
) as stderr_file,
|
44
|
+
):
|
45
|
+
process = subprocess.Popen(
|
46
|
+
[sys.executable],
|
47
|
+
stdin=subprocess.PIPE,
|
48
|
+
stdout=subprocess.PIPE,
|
49
|
+
stderr=subprocess.PIPE,
|
50
|
+
text=True,
|
51
|
+
bufsize=1,
|
52
|
+
universal_newlines=True,
|
53
|
+
encoding="utf-8",
|
54
|
+
env={**dict(), **dict(PYTHONIOENCODING="utf-8")},
|
55
|
+
)
|
56
|
+
stdout_lines = 0
|
57
|
+
stderr_lines = 0
|
58
|
+
|
59
|
+
def stream_output(stream, file_obj, report_func, count_func):
|
60
|
+
nonlocal stdout_lines, stderr_lines
|
61
|
+
for line in stream:
|
62
|
+
file_obj.write(line)
|
63
|
+
file_obj.flush()
|
64
|
+
report_func(line)
|
65
|
+
if count_func == "stdout":
|
66
|
+
stdout_lines += 1
|
67
|
+
else:
|
68
|
+
stderr_lines += 1
|
69
|
+
|
70
|
+
stdout_thread = threading.Thread(
|
71
|
+
target=stream_output,
|
72
|
+
args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
|
73
|
+
)
|
74
|
+
stderr_thread = threading.Thread(
|
75
|
+
target=stream_output,
|
76
|
+
args=(process.stderr, stderr_file, self.report_stderr, "stderr"),
|
77
|
+
)
|
78
|
+
stdout_thread.start()
|
79
|
+
stderr_thread.start()
|
80
|
+
try:
|
81
|
+
process.stdin.write(code)
|
82
|
+
process.stdin.close()
|
83
|
+
return_code = process.wait(timeout=timeout)
|
84
|
+
except subprocess.TimeoutExpired:
|
85
|
+
process.kill()
|
86
|
+
self.report_error(
|
87
|
+
tr("\u274c Timed out after {timeout} seconds.", timeout=timeout)
|
88
|
+
)
|
89
|
+
return tr(
|
90
|
+
"Code timed out after {timeout} seconds.", timeout=timeout
|
91
|
+
)
|
92
|
+
stdout_thread.join()
|
93
|
+
stderr_thread.join()
|
94
|
+
stdout_file.flush()
|
95
|
+
stderr_file.flush()
|
96
|
+
|
97
|
+
self.report_success(
|
98
|
+
tr("\u2705 Return code {return_code}", return_code=return_code)
|
99
|
+
)
|
100
|
+
# Read back the content for summary if not too large
|
101
|
+
with open(
|
102
|
+
stdout_file.name, "r", encoding="utf-8", errors="replace"
|
103
|
+
) as out_f:
|
104
|
+
stdout_content = out_f.read()
|
105
|
+
with open(
|
106
|
+
stderr_file.name, "r", encoding="utf-8", errors="replace"
|
107
|
+
) as err_f:
|
108
|
+
stderr_content = err_f.read()
|
109
|
+
max_lines = 100
|
110
|
+
stdout_lines = stdout_content.count("\n")
|
111
|
+
stderr_lines = stderr_content.count("\n")
|
112
|
+
|
113
|
+
def head_tail(text, n=10):
|
114
|
+
lines = text.splitlines()
|
115
|
+
if len(lines) <= 2 * n:
|
116
|
+
return "\n".join(lines)
|
117
|
+
return "\n".join(
|
118
|
+
lines[:n]
|
119
|
+
+ ["... ({} lines omitted) ...".format(len(lines) - 2 * n)]
|
120
|
+
+ lines[-n:]
|
121
|
+
)
|
122
|
+
|
123
|
+
if stdout_lines <= max_lines and stderr_lines <= max_lines:
|
124
|
+
result = (
|
125
|
+
f"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}"
|
126
|
+
)
|
127
|
+
if stderr_content.strip():
|
128
|
+
result += f"\n--- STDERR ---\n{stderr_content}"
|
129
|
+
return result
|
130
|
+
else:
|
131
|
+
result = (
|
132
|
+
f"stdout_file: {stdout_file.name} (lines: {stdout_lines})\n"
|
133
|
+
)
|
134
|
+
if stderr_lines > 0 and stderr_content.strip():
|
135
|
+
result += (
|
136
|
+
f"stderr_file: {stderr_file.name} (lines: {stderr_lines})\n"
|
137
|
+
)
|
138
|
+
result += f"returncode: {return_code}\n"
|
139
|
+
result += (
|
140
|
+
"--- STDOUT (head/tail) ---\n"
|
141
|
+
+ head_tail(stdout_content)
|
142
|
+
+ "\n"
|
143
|
+
)
|
144
|
+
if stderr_content.strip():
|
145
|
+
result += (
|
146
|
+
"--- STDERR (head/tail) ---\n"
|
147
|
+
+ head_tail(stderr_content)
|
148
|
+
+ "\n"
|
149
|
+
)
|
150
|
+
result += "Use the get_lines tool to inspect the contents of these files when needed."
|
151
|
+
return result
|
152
|
+
except Exception as e:
|
153
|
+
self.report_error(tr("\u274c Error: {error}", error=e))
|
154
|
+
return tr("Error running code via stdin: {error}", error=e)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from janito.agent.tool_base import ToolBase
|
2
|
+
from janito.agent.tools_utils.action_type import ActionType
|
2
3
|
from janito.agent.tool_registry import register_tool
|
3
|
-
from janito.agent.
|
4
|
+
from janito.agent.tools_utils.utils import pluralize, display_path
|
4
5
|
from janito.i18n import tr
|
5
6
|
import shutil
|
6
7
|
import os
|
@@ -24,7 +25,8 @@ class RemoveDirectoryTool(ToolBase):
|
|
24
25
|
def run(self, file_path: str, recursive: bool = False) -> str:
|
25
26
|
disp_path = display_path(file_path)
|
26
27
|
self.report_info(
|
27
|
-
|
28
|
+
ActionType.WRITE,
|
29
|
+
tr("🗃️ Removing directory '{disp_path}' ...", disp_path=disp_path),
|
28
30
|
)
|
29
31
|
backup_zip = None
|
30
32
|
try:
|