janito 1.12.2__py3-none-any.whl → 1.13.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/tools/python_command_runner.py +80 -28
- janito/agent/tools/python_file_runner.py +79 -27
- janito/agent/tools/python_stdin_runner.py +83 -28
- janito/agent/tools/replace_text_in_file.py +5 -7
- janito/agent/tools/run_bash_command.py +172 -82
- janito/agent/tools/run_powershell_command.py +149 -111
- janito/agent/tools/search_text/core.py +1 -1
- janito/agent/tools/search_text/pattern_utils.py +1 -1
- janito/agent/tools/validate_file_syntax/core.py +8 -8
- janito/agent/tools/validate_file_syntax/markdown_validator.py +2 -2
- janito/cli/arg_parser.py +5 -0
- janito/cli/cli_main.py +28 -17
- janito/shell/commands/utility.py +3 -0
- janito/shell/main.py +56 -31
- {janito-1.12.2.dist-info → janito-1.13.0.dist-info}/METADATA +1 -1
- {janito-1.12.2.dist-info → janito-1.13.0.dist-info}/RECORD +21 -22
- {janito-1.12.2.dist-info → janito-1.13.0.dist-info}/WHEEL +1 -1
- janito/shell/commands.py +0 -40
- {janito-1.12.2.dist-info → janito-1.13.0.dist-info}/entry_points.txt +0 -0
- {janito-1.12.2.dist-info → janito-1.13.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.12.2.dist-info → janito-1.13.0.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,8 @@ import subprocess
|
|
6
6
|
import tempfile
|
7
7
|
import sys
|
8
8
|
import os
|
9
|
+
import threading
|
10
|
+
from janito.agent.runtime_config import runtime_config
|
9
11
|
|
10
12
|
|
11
13
|
@register_tool(name="run_bash_command")
|
@@ -22,6 +24,156 @@ class RunBashCommandTool(ToolBase):
|
|
22
24
|
str: File paths and line counts for stdout and stderr.
|
23
25
|
"""
|
24
26
|
|
27
|
+
def _stream_output(
|
28
|
+
self,
|
29
|
+
stream,
|
30
|
+
report_func,
|
31
|
+
accum=None,
|
32
|
+
file_obj=None,
|
33
|
+
count_func=None,
|
34
|
+
counter=None,
|
35
|
+
):
|
36
|
+
for line in stream:
|
37
|
+
if accum is not None:
|
38
|
+
accum.append(line)
|
39
|
+
if file_obj is not None:
|
40
|
+
file_obj.write(line)
|
41
|
+
file_obj.flush()
|
42
|
+
report_func(line)
|
43
|
+
if counter is not None and count_func is not None:
|
44
|
+
counter[count_func] += 1
|
45
|
+
|
46
|
+
def _handle_all_out(self, process, timeout):
|
47
|
+
stdout_accum = []
|
48
|
+
stderr_accum = []
|
49
|
+
stdout_thread = threading.Thread(
|
50
|
+
target=self._stream_output,
|
51
|
+
args=(process.stdout, self.report_stdout, stdout_accum),
|
52
|
+
)
|
53
|
+
stderr_thread = threading.Thread(
|
54
|
+
target=self._stream_output,
|
55
|
+
args=(process.stderr, self.report_stderr, stderr_accum),
|
56
|
+
)
|
57
|
+
stdout_thread.start()
|
58
|
+
stderr_thread.start()
|
59
|
+
try:
|
60
|
+
return_code = process.wait(timeout=timeout)
|
61
|
+
except subprocess.TimeoutExpired:
|
62
|
+
process.kill()
|
63
|
+
self.report_error(
|
64
|
+
tr(" ❌ Timed out after {timeout} seconds.", timeout=timeout)
|
65
|
+
)
|
66
|
+
return tr("Command timed out after {timeout} seconds.", timeout=timeout)
|
67
|
+
stdout_thread.join()
|
68
|
+
stderr_thread.join()
|
69
|
+
self.report_success(
|
70
|
+
tr(" ✅ return code {return_code}", return_code=return_code)
|
71
|
+
)
|
72
|
+
stdout_content = "".join(stdout_accum)
|
73
|
+
stderr_content = "".join(stderr_accum)
|
74
|
+
result = tr(
|
75
|
+
"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
|
76
|
+
return_code=return_code,
|
77
|
+
stdout_content=stdout_content,
|
78
|
+
)
|
79
|
+
if stderr_content.strip():
|
80
|
+
result += tr(
|
81
|
+
"\n--- STDERR ---\n{stderr_content}", stderr_content=stderr_content
|
82
|
+
)
|
83
|
+
return result
|
84
|
+
|
85
|
+
def _handle_file_out(self, process, timeout):
|
86
|
+
max_lines = 100
|
87
|
+
with (
|
88
|
+
tempfile.NamedTemporaryFile(
|
89
|
+
mode="w+",
|
90
|
+
prefix="run_bash_stdout_",
|
91
|
+
delete=False,
|
92
|
+
encoding="utf-8",
|
93
|
+
) as stdout_file,
|
94
|
+
tempfile.NamedTemporaryFile(
|
95
|
+
mode="w+",
|
96
|
+
prefix="run_bash_stderr_",
|
97
|
+
delete=False,
|
98
|
+
encoding="utf-8",
|
99
|
+
) as stderr_file,
|
100
|
+
):
|
101
|
+
counter = {"stdout": 0, "stderr": 0}
|
102
|
+
stdout_thread = threading.Thread(
|
103
|
+
target=self._stream_output,
|
104
|
+
args=(
|
105
|
+
process.stdout,
|
106
|
+
self.report_stdout,
|
107
|
+
None,
|
108
|
+
stdout_file,
|
109
|
+
"stdout",
|
110
|
+
counter,
|
111
|
+
),
|
112
|
+
)
|
113
|
+
stderr_thread = threading.Thread(
|
114
|
+
target=self._stream_output,
|
115
|
+
args=(
|
116
|
+
process.stderr,
|
117
|
+
self.report_stderr,
|
118
|
+
None,
|
119
|
+
stderr_file,
|
120
|
+
"stderr",
|
121
|
+
counter,
|
122
|
+
),
|
123
|
+
)
|
124
|
+
stdout_thread.start()
|
125
|
+
stderr_thread.start()
|
126
|
+
try:
|
127
|
+
return_code = process.wait(timeout=timeout)
|
128
|
+
except subprocess.TimeoutExpired:
|
129
|
+
process.kill()
|
130
|
+
self.report_error(
|
131
|
+
tr(" ❌ Timed out after {timeout} seconds.", timeout=timeout)
|
132
|
+
)
|
133
|
+
return tr("Command timed out after {timeout} seconds.", timeout=timeout)
|
134
|
+
stdout_thread.join()
|
135
|
+
stderr_thread.join()
|
136
|
+
stdout_file.flush()
|
137
|
+
stderr_file.flush()
|
138
|
+
self.report_success(
|
139
|
+
tr(" ✅ return code {return_code}", return_code=return_code)
|
140
|
+
)
|
141
|
+
stdout_file.seek(0)
|
142
|
+
stderr_file.seek(0)
|
143
|
+
stdout_content = stdout_file.read()
|
144
|
+
stderr_content = stderr_file.read()
|
145
|
+
stdout_lines = stdout_content.count("\n")
|
146
|
+
stderr_lines = stderr_content.count("\n")
|
147
|
+
if stdout_lines <= max_lines and stderr_lines <= max_lines:
|
148
|
+
result = tr(
|
149
|
+
"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
|
150
|
+
return_code=return_code,
|
151
|
+
stdout_content=stdout_content,
|
152
|
+
)
|
153
|
+
if stderr_content.strip():
|
154
|
+
result += tr(
|
155
|
+
"\n--- STDERR ---\n{stderr_content}",
|
156
|
+
stderr_content=stderr_content,
|
157
|
+
)
|
158
|
+
return result
|
159
|
+
else:
|
160
|
+
result = tr(
|
161
|
+
"[LARGE OUTPUT]\nstdout_file: {stdout_file} (lines: {stdout_lines})\n",
|
162
|
+
stdout_file=stdout_file.name,
|
163
|
+
stdout_lines=stdout_lines,
|
164
|
+
)
|
165
|
+
if stderr_lines > 0:
|
166
|
+
result += tr(
|
167
|
+
"stderr_file: {stderr_file} (lines: {stderr_lines})\n",
|
168
|
+
stderr_file=stderr_file.name,
|
169
|
+
stderr_lines=stderr_lines,
|
170
|
+
)
|
171
|
+
result += tr(
|
172
|
+
"returncode: {return_code}\nUse the get_lines tool to inspect the contents of these files when needed.",
|
173
|
+
return_code=return_code,
|
174
|
+
)
|
175
|
+
return result
|
176
|
+
|
25
177
|
def run(
|
26
178
|
self,
|
27
179
|
command: str,
|
@@ -30,7 +182,7 @@ class RunBashCommandTool(ToolBase):
|
|
30
182
|
requires_user_input: bool = False,
|
31
183
|
) -> str:
|
32
184
|
if not command.strip():
|
33
|
-
self.report_warning(tr("
|
185
|
+
self.report_warning(tr("ℹ️ Empty command provided."))
|
34
186
|
return tr("Warning: Empty command provided. Operation skipped.")
|
35
187
|
self.report_info(
|
36
188
|
ActionType.EXECUTE,
|
@@ -39,90 +191,28 @@ class RunBashCommandTool(ToolBase):
|
|
39
191
|
if requires_user_input:
|
40
192
|
self.report_warning(
|
41
193
|
tr(
|
42
|
-
"
|
194
|
+
"⚠️ Warning: This command might be interactive, require user input, and might hang."
|
43
195
|
)
|
44
196
|
)
|
45
197
|
sys.stdout.flush()
|
46
198
|
try:
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
env
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
encoding="utf-8",
|
65
|
-
bufsize=1,
|
66
|
-
env=env,
|
67
|
-
)
|
68
|
-
try:
|
69
|
-
stdout_content, stderr_content = process.communicate(
|
70
|
-
timeout=timeout
|
71
|
-
)
|
72
|
-
except subprocess.TimeoutExpired:
|
73
|
-
process.kill()
|
74
|
-
self.report_error(
|
75
|
-
tr(
|
76
|
-
" \u274c Timed out after {timeout} seconds.",
|
77
|
-
timeout=timeout,
|
78
|
-
)
|
79
|
-
)
|
80
|
-
return tr(
|
81
|
-
"Command timed out after {timeout} seconds.", timeout=timeout
|
82
|
-
)
|
83
|
-
self.report_success(
|
84
|
-
tr(
|
85
|
-
" \u2705 return code {return_code}",
|
86
|
-
return_code=process.returncode,
|
87
|
-
)
|
88
|
-
)
|
89
|
-
warning_msg = ""
|
90
|
-
if requires_user_input:
|
91
|
-
warning_msg = tr(
|
92
|
-
"\u26a0\ufe0f Warning: This command might be interactive, require user input, and might hang.\n"
|
93
|
-
)
|
94
|
-
max_lines = 100
|
95
|
-
stdout_lines = stdout_content.count("\n")
|
96
|
-
stderr_lines = stderr_content.count("\n")
|
97
|
-
if stdout_lines <= max_lines and stderr_lines <= max_lines:
|
98
|
-
result = warning_msg + tr(
|
99
|
-
"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
|
100
|
-
return_code=process.returncode,
|
101
|
-
stdout_content=stdout_content,
|
102
|
-
)
|
103
|
-
if stderr_content.strip():
|
104
|
-
result += tr(
|
105
|
-
"\n--- STDERR ---\n{stderr_content}",
|
106
|
-
stderr_content=stderr_content,
|
107
|
-
)
|
108
|
-
return result
|
109
|
-
else:
|
110
|
-
result = warning_msg + tr(
|
111
|
-
"[LARGE OUTPUT]\nstdout_file: {stdout_file} (lines: {stdout_lines})\n",
|
112
|
-
stdout_file=stdout_file.name,
|
113
|
-
stdout_lines=stdout_lines,
|
114
|
-
)
|
115
|
-
if stderr_lines > 0:
|
116
|
-
result += tr(
|
117
|
-
"stderr_file: {stderr_file} (lines: {stderr_lines})\n",
|
118
|
-
stderr_file=stderr_file.name,
|
119
|
-
stderr_lines=stderr_lines,
|
120
|
-
)
|
121
|
-
result += tr(
|
122
|
-
"returncode: {return_code}\nUse the get_lines tool to inspect the contents of these files when needed.",
|
123
|
-
return_code=process.returncode,
|
124
|
-
)
|
125
|
-
return result
|
199
|
+
env = os.environ.copy()
|
200
|
+
env["PYTHONIOENCODING"] = "utf-8"
|
201
|
+
env["LC_ALL"] = "C.UTF-8"
|
202
|
+
env["LANG"] = "C.UTF-8"
|
203
|
+
process = subprocess.Popen(
|
204
|
+
["bash", "-c", command],
|
205
|
+
stdout=subprocess.PIPE,
|
206
|
+
stderr=subprocess.PIPE,
|
207
|
+
text=True,
|
208
|
+
encoding="utf-8",
|
209
|
+
bufsize=1,
|
210
|
+
env=env,
|
211
|
+
)
|
212
|
+
if runtime_config.get("all_out"):
|
213
|
+
return self._handle_all_out(process, timeout)
|
214
|
+
else:
|
215
|
+
return self._handle_file_out(process, timeout)
|
126
216
|
except Exception as e:
|
127
|
-
self.report_error(tr("
|
217
|
+
self.report_error(tr(" ❌ Error: {error}", error=e))
|
128
218
|
return tr("Error running command: {error}", error=e)
|
@@ -6,6 +6,7 @@ import subprocess
|
|
6
6
|
import os
|
7
7
|
import tempfile
|
8
8
|
import threading
|
9
|
+
from janito.agent.runtime_config import runtime_config
|
9
10
|
|
10
11
|
|
11
12
|
@register_tool(name="run_powershell_command")
|
@@ -29,7 +30,7 @@ class RunPowerShellCommandTool(ToolBase):
|
|
29
30
|
if requires_user_input:
|
30
31
|
self.report_warning(
|
31
32
|
tr(
|
32
|
-
"
|
33
|
+
"⚠️ Warning: This command might be interactive, require user input, and might hang."
|
33
34
|
)
|
34
35
|
)
|
35
36
|
if require_confirmation:
|
@@ -40,7 +41,7 @@ class RunPowerShellCommandTool(ToolBase):
|
|
40
41
|
)
|
41
42
|
)
|
42
43
|
if not confirmed:
|
43
|
-
self.report_warning(tr("
|
44
|
+
self.report_warning(tr("⚠️ Execution cancelled by user."))
|
44
45
|
return False
|
45
46
|
return True
|
46
47
|
|
@@ -65,59 +66,114 @@ class RunPowerShellCommandTool(ToolBase):
|
|
65
66
|
env=env,
|
66
67
|
)
|
67
68
|
|
68
|
-
def _stream_output(
|
69
|
+
def _stream_output(
|
70
|
+
self,
|
71
|
+
stream,
|
72
|
+
report_func,
|
73
|
+
accum=None,
|
74
|
+
file_obj=None,
|
75
|
+
count_func=None,
|
76
|
+
counter=None,
|
77
|
+
):
|
69
78
|
for line in stream:
|
70
|
-
|
71
|
-
|
79
|
+
if accum is not None:
|
80
|
+
accum.append(line)
|
81
|
+
if file_obj is not None:
|
82
|
+
file_obj.write(line)
|
83
|
+
file_obj.flush()
|
72
84
|
report_func(line)
|
73
|
-
if count_func
|
74
|
-
counter[
|
75
|
-
else:
|
76
|
-
counter["stderr"] += 1
|
85
|
+
if counter is not None and count_func is not None:
|
86
|
+
counter[count_func] += 1
|
77
87
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
88
|
+
def _handle_all_out(self, process, timeout):
|
89
|
+
stdout_accum = []
|
90
|
+
stderr_accum = []
|
91
|
+
stdout_thread = threading.Thread(
|
92
|
+
target=self._stream_output,
|
93
|
+
args=(process.stdout, self.report_stdout, stdout_accum),
|
94
|
+
)
|
95
|
+
stderr_thread = threading.Thread(
|
96
|
+
target=self._stream_output,
|
97
|
+
args=(process.stderr, self.report_stderr, stderr_accum),
|
98
|
+
)
|
99
|
+
stdout_thread.start()
|
100
|
+
stderr_thread.start()
|
101
|
+
try:
|
102
|
+
return_code = process.wait(timeout=timeout)
|
103
|
+
except subprocess.TimeoutExpired:
|
104
|
+
process.kill()
|
105
|
+
self.report_error(
|
106
|
+
tr(" ❌ Timed out after {timeout} seconds.", timeout=timeout)
|
85
107
|
)
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
108
|
+
return tr("Command timed out after {timeout} seconds.", timeout=timeout)
|
109
|
+
stdout_thread.join()
|
110
|
+
stderr_thread.join()
|
111
|
+
self.report_success(
|
112
|
+
tr(" ✅ return code {return_code}", return_code=return_code)
|
113
|
+
)
|
114
|
+
stdout = "".join(stdout_accum)
|
115
|
+
stderr = "".join(stderr_accum)
|
116
|
+
result = f"Return code: {return_code}\n--- STDOUT ---\n{stdout}"
|
117
|
+
if stderr and stderr.strip():
|
118
|
+
result += f"\n--- STDERR ---\n{stderr}"
|
119
|
+
return result
|
120
|
+
|
121
|
+
def _handle_file_out(self, process, timeout):
|
122
|
+
with (
|
123
|
+
tempfile.NamedTemporaryFile(
|
124
|
+
mode="w+",
|
125
|
+
prefix="run_powershell_stdout_",
|
126
|
+
delete=False,
|
127
|
+
encoding="utf-8",
|
128
|
+
) as stdout_file,
|
129
|
+
tempfile.NamedTemporaryFile(
|
130
|
+
mode="w+",
|
131
|
+
prefix="run_powershell_stderr_",
|
132
|
+
delete=False,
|
133
|
+
encoding="utf-8",
|
134
|
+
) as stderr_file,
|
135
|
+
):
|
136
|
+
counter = {"stdout": 0, "stderr": 0}
|
137
|
+
stdout_thread = threading.Thread(
|
138
|
+
target=self._stream_output,
|
139
|
+
args=(
|
140
|
+
process.stdout,
|
141
|
+
self.report_stdout,
|
142
|
+
None,
|
143
|
+
stdout_file,
|
144
|
+
"stdout",
|
145
|
+
counter,
|
146
|
+
),
|
97
147
|
)
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
stdout_lines=stdout_lines,
|
148
|
+
stderr_thread = threading.Thread(
|
149
|
+
target=self._stream_output,
|
150
|
+
args=(
|
151
|
+
process.stderr,
|
152
|
+
self.report_stderr,
|
153
|
+
None,
|
154
|
+
stderr_file,
|
155
|
+
"stderr",
|
156
|
+
counter,
|
157
|
+
),
|
109
158
|
)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
159
|
+
stdout_thread.start()
|
160
|
+
stderr_thread.start()
|
161
|
+
try:
|
162
|
+
return_code = process.wait(timeout=timeout)
|
163
|
+
except subprocess.TimeoutExpired:
|
164
|
+
process.kill()
|
165
|
+
self.report_error(
|
166
|
+
tr(" ❌ Timed out after {timeout} seconds.", timeout=timeout)
|
115
167
|
)
|
116
|
-
|
117
|
-
|
118
|
-
|
168
|
+
return tr("Command timed out after {timeout} seconds.", timeout=timeout)
|
169
|
+
stdout_thread.join()
|
170
|
+
stderr_thread.join()
|
171
|
+
stdout_file.flush()
|
172
|
+
stderr_file.flush()
|
173
|
+
self.report_success(
|
174
|
+
tr(" ✅ return code {return_code}", return_code=return_code)
|
119
175
|
)
|
120
|
-
return
|
176
|
+
return self._format_result(stdout_file.name, stderr_file.name, return_code)
|
121
177
|
|
122
178
|
def run(
|
123
179
|
self,
|
@@ -127,87 +183,69 @@ class RunPowerShellCommandTool(ToolBase):
|
|
127
183
|
requires_user_input: bool = False,
|
128
184
|
) -> str:
|
129
185
|
if not command.strip():
|
130
|
-
self.report_warning(tr("
|
186
|
+
self.report_warning(tr("ℹ️ Empty command provided."))
|
131
187
|
return tr("Warning: Empty command provided. Operation skipped.")
|
132
188
|
encoding_prefix = "$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
|
133
189
|
command_with_encoding = encoding_prefix + command
|
134
190
|
self.report_info(
|
135
191
|
ActionType.EXECUTE,
|
136
192
|
tr(
|
137
|
-
"
|
193
|
+
"🖥️ Running PowerShell command: {command} ...\n",
|
138
194
|
command=command,
|
139
195
|
),
|
140
196
|
)
|
141
197
|
if not self._confirm_and_warn(
|
142
198
|
command, require_confirmation, requires_user_input
|
143
199
|
):
|
144
|
-
return tr("
|
200
|
+
return tr("❌ Command execution cancelled by user.")
|
145
201
|
from janito.agent.platform_discovery import PlatformDiscovery
|
146
202
|
|
147
203
|
pd = PlatformDiscovery()
|
148
204
|
shell_exe = "powershell.exe" if pd.is_windows() else "pwsh"
|
149
205
|
try:
|
150
|
-
|
151
|
-
tempfile.NamedTemporaryFile(
|
152
|
-
mode="w+",
|
153
|
-
prefix="run_powershell_stdout_",
|
154
|
-
delete=False,
|
155
|
-
encoding="utf-8",
|
156
|
-
) as stdout_file,
|
157
|
-
tempfile.NamedTemporaryFile(
|
158
|
-
mode="w+",
|
159
|
-
prefix="run_powershell_stderr_",
|
160
|
-
delete=False,
|
161
|
-
encoding="utf-8",
|
162
|
-
) as stderr_file,
|
163
|
-
):
|
206
|
+
if runtime_config.get("all_out"):
|
164
207
|
process = self._launch_process(shell_exe, command_with_encoding)
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
process.stdout,
|
170
|
-
stdout_file,
|
171
|
-
self.report_stdout,
|
172
|
-
"stdout",
|
173
|
-
counter,
|
174
|
-
),
|
175
|
-
)
|
176
|
-
stderr_thread = threading.Thread(
|
177
|
-
target=self._stream_output,
|
178
|
-
args=(
|
179
|
-
process.stderr,
|
180
|
-
stderr_file,
|
181
|
-
self.report_stderr,
|
182
|
-
"stderr",
|
183
|
-
counter,
|
184
|
-
),
|
185
|
-
)
|
186
|
-
stdout_thread.start()
|
187
|
-
stderr_thread.start()
|
188
|
-
try:
|
189
|
-
return_code = process.wait(timeout=timeout)
|
190
|
-
except subprocess.TimeoutExpired:
|
191
|
-
process.kill()
|
192
|
-
self.report_error(
|
193
|
-
tr(
|
194
|
-
" \u274c Timed out after {timeout} seconds.",
|
195
|
-
timeout=timeout,
|
196
|
-
)
|
197
|
-
)
|
198
|
-
return tr(
|
199
|
-
"Command timed out after {timeout} seconds.", timeout=timeout
|
200
|
-
)
|
201
|
-
stdout_thread.join()
|
202
|
-
stderr_thread.join()
|
203
|
-
stdout_file.flush()
|
204
|
-
stderr_file.flush()
|
205
|
-
self.report_success(
|
206
|
-
tr(" \u2705 return code {return_code}", return_code=return_code)
|
207
|
-
)
|
208
|
-
return self._format_result(
|
209
|
-
requires_user_input, return_code, stdout_file, stderr_file
|
210
|
-
)
|
208
|
+
return self._handle_all_out(process, timeout)
|
209
|
+
else:
|
210
|
+
process = self._launch_process(shell_exe, command_with_encoding)
|
211
|
+
return self._handle_file_out(process, timeout)
|
211
212
|
except Exception as e:
|
212
|
-
self.report_error(tr("
|
213
|
+
self.report_error(tr(" ❌ Error: {error}", error=e))
|
213
214
|
return tr("Error running command: {error}", error=e)
|
215
|
+
|
216
|
+
def _format_result(self, stdout_file_name, stderr_file_name, return_code):
|
217
|
+
with open(stdout_file_name, "r", encoding="utf-8", errors="replace") as out_f:
|
218
|
+
stdout_content = out_f.read()
|
219
|
+
with open(stderr_file_name, "r", encoding="utf-8", errors="replace") as err_f:
|
220
|
+
stderr_content = err_f.read()
|
221
|
+
max_lines = 100
|
222
|
+
stdout_lines = stdout_content.count("\n")
|
223
|
+
stderr_lines = stderr_content.count("\n")
|
224
|
+
|
225
|
+
def head_tail(text, n=10):
|
226
|
+
lines = text.splitlines()
|
227
|
+
if len(lines) <= 2 * n:
|
228
|
+
return "\n".join(lines)
|
229
|
+
return "\n".join(
|
230
|
+
lines[:n]
|
231
|
+
+ ["... ({} lines omitted) ...".format(len(lines) - 2 * n)]
|
232
|
+
+ lines[-n:]
|
233
|
+
)
|
234
|
+
|
235
|
+
if stdout_lines <= max_lines and stderr_lines <= max_lines:
|
236
|
+
result = f"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}"
|
237
|
+
if stderr_content.strip():
|
238
|
+
result += f"\n--- STDERR ---\n{stderr_content}"
|
239
|
+
return result
|
240
|
+
else:
|
241
|
+
result = f"stdout_file: {stdout_file_name} (lines: {stdout_lines})\n"
|
242
|
+
if stderr_lines > 0 and stderr_content.strip():
|
243
|
+
result += f"stderr_file: {stderr_file_name} (lines: {stderr_lines})\n"
|
244
|
+
result += f"returncode: {return_code}\n"
|
245
|
+
result += "--- STDOUT (head/tail) ---\n" + head_tail(stdout_content) + "\n"
|
246
|
+
if stderr_content.strip():
|
247
|
+
result += (
|
248
|
+
"--- STDERR (head/tail) ---\n" + head_tail(stderr_content) + "\n"
|
249
|
+
)
|
250
|
+
result += "Use the get_lines tool to inspect the contents of these files when needed."
|
251
|
+
return result
|
@@ -125,7 +125,7 @@ class SearchTextTool(ToolBase):
|
|
125
125
|
file_word = pluralize("match", count)
|
126
126
|
self.report_success(
|
127
127
|
tr(
|
128
|
-
"
|
128
|
+
" ✅ {count} {file_word}{max_flag}",
|
129
129
|
count=count,
|
130
130
|
file_word=file_word,
|
131
131
|
max_flag=" (max)" if dir_limit_reached else "",
|
@@ -18,7 +18,7 @@ def prepare_pattern(pattern, is_regex, report_error, report_warning):
|
|
18
18
|
regex = re.compile(pattern)
|
19
19
|
use_regex = True
|
20
20
|
except re.error as e:
|
21
|
-
report_warning(tr("
|
21
|
+
report_warning(tr("⚠️ Invalid regex pattern."))
|
22
22
|
return (
|
23
23
|
None,
|
24
24
|
False,
|
@@ -36,7 +36,7 @@ def _get_validator(ext):
|
|
36
36
|
|
37
37
|
|
38
38
|
def _handle_validation_error(e, report_warning):
|
39
|
-
msg = tr("
|
39
|
+
msg = tr("⚠️ Warning: Syntax error: {error}", error=e)
|
40
40
|
if report_warning:
|
41
41
|
report_warning(msg)
|
42
42
|
return msg
|
@@ -51,7 +51,7 @@ def validate_file_syntax(
|
|
51
51
|
if validator:
|
52
52
|
return validator(file_path)
|
53
53
|
else:
|
54
|
-
msg = tr("
|
54
|
+
msg = tr("⚠️ Warning: Unsupported file extension: {ext}", ext=ext)
|
55
55
|
if report_warning:
|
56
56
|
report_warning(msg)
|
57
57
|
return msg
|
@@ -78,9 +78,9 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
78
78
|
file_path (str): Path to the file to validate.
|
79
79
|
Returns:
|
80
80
|
str: Validation status message. Example:
|
81
|
-
- "
|
82
|
-
- "
|
83
|
-
- "
|
81
|
+
- "✅ Syntax OK"
|
82
|
+
- "⚠️ Warning: Syntax error: <error message>"
|
83
|
+
- "⚠️ Warning: Unsupported file extension: <ext>"
|
84
84
|
"""
|
85
85
|
|
86
86
|
def run(self, file_path: str) -> str:
|
@@ -98,8 +98,8 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
98
98
|
report_warning=self.report_warning,
|
99
99
|
report_success=self.report_success,
|
100
100
|
)
|
101
|
-
if result.startswith("
|
101
|
+
if result.startswith("✅"):
|
102
102
|
self.report_success(result)
|
103
|
-
elif result.startswith("
|
104
|
-
self.report_warning(tr("
|
103
|
+
elif result.startswith("⚠️"):
|
104
|
+
self.report_warning(tr("⚠️ ") + result.lstrip("⚠️ "))
|
105
105
|
return result
|
@@ -102,8 +102,8 @@ def _check_unclosed_inline_code(content):
|
|
102
102
|
def _build_markdown_result(errors):
|
103
103
|
if errors:
|
104
104
|
msg = tr(
|
105
|
-
"
|
105
|
+
"⚠️ Warning: Markdown syntax issues found:\n{errors}",
|
106
106
|
errors="\n".join(errors),
|
107
107
|
)
|
108
108
|
return msg
|
109
|
-
return "
|
109
|
+
return "✅ Syntax valid"
|