kader 0.1.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.
@@ -0,0 +1,249 @@
1
+ """
2
+ Command Execution Tool for Agentic Operations.
3
+
4
+ This module provides a tool for executing command line operations safely.
5
+ It includes OS detection and validation to ensure commands are appropriate
6
+ for the host operating system.
7
+ """
8
+
9
+ import platform
10
+ import subprocess
11
+
12
+ from .base import (
13
+ BaseTool,
14
+ ParameterSchema,
15
+ ToolCategory,
16
+ )
17
+
18
+
19
+ class CommandExecutorTool(BaseTool[str]):
20
+ """
21
+ Tool to execute command line operations.
22
+
23
+ Executes commands on the host system with OS-appropriate validation
24
+ and safety checks.
25
+ """
26
+
27
+ def __init__(self) -> None:
28
+ """
29
+ Initialize the command executor tool.
30
+ """
31
+ super().__init__(
32
+ name="execute_command",
33
+ description=(
34
+ "Execute a command line operation on the host system. "
35
+ "Automatically validates commands against the host operating system."
36
+ ),
37
+ parameters=[
38
+ ParameterSchema(
39
+ name="command",
40
+ type="string",
41
+ description="The command to execute on the host system",
42
+ ),
43
+ ParameterSchema(
44
+ name="timeout",
45
+ type="integer",
46
+ description="Timeout for the command in seconds (default 30)",
47
+ required=False,
48
+ default=30,
49
+ minimum=1,
50
+ maximum=300,
51
+ ),
52
+ ],
53
+ category=ToolCategory.UTILITY,
54
+ )
55
+
56
+ # Determine the current OS
57
+ self._host_os = platform.system().lower()
58
+ self._shell = self._get_default_shell()
59
+
60
+ def _get_default_shell(self) -> str:
61
+ """Get the appropriate shell for the current OS."""
62
+ if self._host_os == "windows":
63
+ return "cmd.exe"
64
+ else:
65
+ return "/bin/bash"
66
+
67
+ def _is_command_valid_for_os(self, command: str) -> tuple[bool, str]:
68
+ """
69
+ Check if a command is valid for the current operating system.
70
+
71
+ Args:
72
+ command: The command to validate
73
+
74
+ Returns:
75
+ Tuple of (valid, reason) where reason explains why invalid
76
+ """
77
+ # Detect command type based on OS
78
+ command_parts = command.strip().split()
79
+ if not command_parts:
80
+ return False, "Empty command"
81
+
82
+ first_part = command_parts[0].lower()
83
+
84
+ # Cross-platform command validation
85
+ if self._host_os == "windows":
86
+ # Common Unix/Linux commands that typically won't work on Windows
87
+ unix_commands = [
88
+ "ls",
89
+ "grep",
90
+ "awk",
91
+ "sed",
92
+ "find",
93
+ "chmod",
94
+ "chown",
95
+ "cp",
96
+ "mv",
97
+ "rm",
98
+ "mkdir",
99
+ "rmdir",
100
+ "touch",
101
+ "cat",
102
+ "head",
103
+ "tail",
104
+ "wc",
105
+ "sort",
106
+ "uniq",
107
+ "ps",
108
+ "kill",
109
+ "top",
110
+ "df",
111
+ "du",
112
+ "which",
113
+ "whoami",
114
+ "uname",
115
+ "pwd",
116
+ "man",
117
+ "tar",
118
+ "zip",
119
+ "unzip",
120
+ "curl",
121
+ "wget",
122
+ ]
123
+
124
+ if first_part in unix_commands:
125
+ return False, (
126
+ f"The command '{first_part}' is a Unix/Linux command "
127
+ "and may not be available on Windows. Consider using "
128
+ f"PowerShell or Windows equivalent command."
129
+ )
130
+ else: # Unix-like systems (Linux, macOS)
131
+ # Common Windows commands that won't work on Unix
132
+ windows_commands = [
133
+ "dir",
134
+ "copy",
135
+ "move",
136
+ "del",
137
+ "ren",
138
+ "md",
139
+ "rd",
140
+ "cls",
141
+ "ver",
142
+ "vol",
143
+ "label",
144
+ "attrib",
145
+ "xcopy",
146
+ "robocopy",
147
+ "ipconfig",
148
+ "netstat",
149
+ "tasklist",
150
+ "taskkill",
151
+ ]
152
+
153
+ if first_part in windows_commands:
154
+ return False, (
155
+ f"The command '{first_part}' is a Windows command "
156
+ "and may not be available on this system. Consider using "
157
+ f"Unix equivalent command."
158
+ )
159
+
160
+ # Special handling for PowerShell commands
161
+ if first_part in [
162
+ "get-command",
163
+ "get-help",
164
+ "get-process",
165
+ "stop-process",
166
+ "get-service",
167
+ "start-service",
168
+ ]:
169
+ return False, (
170
+ f"The command '{first_part}' is a PowerShell command "
171
+ "and may not be available on this Unix-like system."
172
+ )
173
+
174
+ return True, ""
175
+
176
+ def execute(self, command: str, timeout: int = 30) -> str:
177
+ """
178
+ Execute a command on the host system.
179
+
180
+ Args:
181
+ command: The command to execute
182
+ timeout: Timeout in seconds
183
+
184
+ Returns:
185
+ Command execution output as string
186
+ """
187
+ try:
188
+ # Validate the command against the current OS
189
+ is_valid, reason = self._is_command_valid_for_os(command)
190
+ if not is_valid:
191
+ return f"Validation Error: {reason}"
192
+
193
+ # Execute the command
194
+ if self._host_os == "windows":
195
+ # On Windows, use shell=True to allow for more complex commands
196
+ result = subprocess.run(
197
+ command, shell=True, capture_output=True, text=True, timeout=timeout
198
+ )
199
+ else:
200
+ # On Unix-like systems, use the appropriate shell
201
+ result = subprocess.run(
202
+ command,
203
+ shell=True, # Using shell=True to handle pipes and redirections
204
+ capture_output=True,
205
+ text=True,
206
+ timeout=timeout,
207
+ executable=self._shell,
208
+ )
209
+
210
+ # Format result as a string
211
+ if result.returncode == 0:
212
+ output = result.stdout.strip()
213
+ if output:
214
+ return f"Command executed successfully:\n{output}"
215
+ else:
216
+ return "Command executed successfully (no output)"
217
+ else:
218
+ stdout = result.stdout.strip()
219
+ stderr = result.stderr.strip()
220
+
221
+ output_parts = [f"Command failed with exit code {result.returncode}"]
222
+ if stdout:
223
+ output_parts.append(stdout)
224
+ if stderr:
225
+ output_parts.append(stderr)
226
+
227
+ return (
228
+ ":\n".join(output_parts)
229
+ if len(output_parts) > 1
230
+ else output_parts[0]
231
+ )
232
+
233
+ except subprocess.TimeoutExpired:
234
+ return f"Command timed out after {timeout} seconds"
235
+ except Exception as e:
236
+ return f"Execution Error: {str(e)}"
237
+
238
+ async def aexecute(self, command: str, timeout: int = 30) -> str:
239
+ """Async version of execute."""
240
+ import asyncio
241
+
242
+ # Since subprocess.run is blocking, we run it in a thread pool
243
+ loop = asyncio.get_event_loop()
244
+ result = await loop.run_in_executor(None, self.execute, command, timeout)
245
+ return result
246
+
247
+ def get_interruption_message(self, command: str, **kwargs) -> str:
248
+ """Get interruption message for user confirmation."""
249
+ return f"execute {self.name}: {command}"