kader 0.1.5__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.
- cli/README.md +169 -0
- cli/__init__.py +5 -0
- cli/__main__.py +6 -0
- cli/app.py +707 -0
- cli/app.tcss +664 -0
- cli/utils.py +68 -0
- cli/widgets/__init__.py +13 -0
- cli/widgets/confirmation.py +309 -0
- cli/widgets/conversation.py +55 -0
- cli/widgets/loading.py +59 -0
- kader/__init__.py +22 -0
- kader/agent/__init__.py +8 -0
- kader/agent/agents.py +126 -0
- kader/agent/base.py +927 -0
- kader/agent/logger.py +170 -0
- kader/config.py +139 -0
- kader/memory/__init__.py +66 -0
- kader/memory/conversation.py +409 -0
- kader/memory/session.py +385 -0
- kader/memory/state.py +211 -0
- kader/memory/types.py +116 -0
- kader/prompts/__init__.py +9 -0
- kader/prompts/agent_prompts.py +27 -0
- kader/prompts/base.py +81 -0
- kader/prompts/templates/planning_agent.j2 +26 -0
- kader/prompts/templates/react_agent.j2 +18 -0
- kader/providers/__init__.py +9 -0
- kader/providers/base.py +581 -0
- kader/providers/mock.py +96 -0
- kader/providers/ollama.py +447 -0
- kader/tools/README.md +483 -0
- kader/tools/__init__.py +130 -0
- kader/tools/base.py +955 -0
- kader/tools/exec_commands.py +249 -0
- kader/tools/filesys.py +650 -0
- kader/tools/filesystem.py +607 -0
- kader/tools/protocol.py +456 -0
- kader/tools/rag.py +555 -0
- kader/tools/todo.py +210 -0
- kader/tools/utils.py +456 -0
- kader/tools/web.py +246 -0
- kader-0.1.5.dist-info/METADATA +321 -0
- kader-0.1.5.dist-info/RECORD +45 -0
- kader-0.1.5.dist-info/WHEEL +4 -0
- kader-0.1.5.dist-info/entry_points.txt +2 -0
|
@@ -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}"
|