skilllite 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.
- skilllite/__init__.py +159 -0
- skilllite/analyzer.py +391 -0
- skilllite/builtin_tools.py +240 -0
- skilllite/cli.py +217 -0
- skilllite/core/__init__.py +65 -0
- skilllite/core/executor.py +182 -0
- skilllite/core/handler.py +332 -0
- skilllite/core/loops.py +770 -0
- skilllite/core/manager.py +507 -0
- skilllite/core/metadata.py +338 -0
- skilllite/core/prompt_builder.py +321 -0
- skilllite/core/registry.py +185 -0
- skilllite/core/skill_info.py +181 -0
- skilllite/core/tool_builder.py +338 -0
- skilllite/core/tools.py +253 -0
- skilllite/mcp/__init__.py +45 -0
- skilllite/mcp/server.py +734 -0
- skilllite/quick.py +420 -0
- skilllite/sandbox/__init__.py +36 -0
- skilllite/sandbox/base.py +93 -0
- skilllite/sandbox/config.py +229 -0
- skilllite/sandbox/skillbox/__init__.py +44 -0
- skilllite/sandbox/skillbox/binary.py +421 -0
- skilllite/sandbox/skillbox/executor.py +608 -0
- skilllite/sandbox/utils.py +77 -0
- skilllite/validation.py +137 -0
- skilllite-0.1.0.dist-info/METADATA +293 -0
- skilllite-0.1.0.dist-info/RECORD +32 -0
- skilllite-0.1.0.dist-info/WHEEL +5 -0
- skilllite-0.1.0.dist-info/entry_points.txt +3 -0
- skilllite-0.1.0.dist-info/licenses/LICENSE +21 -0
- skilllite-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in tools for SkillLite SDK.
|
|
3
|
+
|
|
4
|
+
This module provides commonly needed tools like file operations
|
|
5
|
+
that can be used with create_enhanced_agentic_loop.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_builtin_file_tools() -> List[Dict[str, Any]]:
|
|
14
|
+
"""
|
|
15
|
+
Get built-in file operation tools.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
List of tool definitions in OpenAI-compatible format
|
|
19
|
+
"""
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
"type": "function",
|
|
23
|
+
"function": {
|
|
24
|
+
"name": "read_file",
|
|
25
|
+
"description": "Read the content of a file",
|
|
26
|
+
"parameters": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"file_path": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Path to the file to read (relative or absolute)"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": ["file_path"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "function",
|
|
40
|
+
"function": {
|
|
41
|
+
"name": "write_file",
|
|
42
|
+
"description": "Write content to a file. Creates the file if it doesn't exist, overwrites if it does.",
|
|
43
|
+
"parameters": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"file_path": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Path to the file to write (relative or absolute)"
|
|
49
|
+
},
|
|
50
|
+
"content": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Content to write to the file"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"required": ["file_path", "content"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"type": "function",
|
|
61
|
+
"function": {
|
|
62
|
+
"name": "list_directory",
|
|
63
|
+
"description": "List files and directories in a given path",
|
|
64
|
+
"parameters": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"directory_path": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Path to the directory to list (relative or absolute)"
|
|
70
|
+
},
|
|
71
|
+
"recursive": {
|
|
72
|
+
"type": "boolean",
|
|
73
|
+
"description": "Whether to list recursively (default: false)",
|
|
74
|
+
"default": False
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"required": ["directory_path"]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "function",
|
|
83
|
+
"function": {
|
|
84
|
+
"name": "file_exists",
|
|
85
|
+
"description": "Check if a file or directory exists",
|
|
86
|
+
"parameters": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"file_path": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"description": "Path to check (relative or absolute)"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"required": ["file_path"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def execute_builtin_file_tool(tool_name: str, tool_input: Dict[str, Any]) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Execute a built-in file operation tool.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
tool_name: Name of the tool to execute
|
|
107
|
+
tool_input: Input parameters for the tool
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Result of the tool execution as a string
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If tool_name is not recognized
|
|
114
|
+
Exception: If tool execution fails
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
if tool_name == "read_file":
|
|
118
|
+
return _read_file(tool_input["file_path"])
|
|
119
|
+
elif tool_name == "write_file":
|
|
120
|
+
return _write_file(tool_input["file_path"], tool_input["content"])
|
|
121
|
+
elif tool_name == "list_directory":
|
|
122
|
+
recursive = tool_input.get("recursive", False)
|
|
123
|
+
return _list_directory(tool_input["directory_path"], recursive)
|
|
124
|
+
elif tool_name == "file_exists":
|
|
125
|
+
return _file_exists(tool_input["file_path"])
|
|
126
|
+
else:
|
|
127
|
+
raise ValueError(f"Unknown built-in tool: {tool_name}")
|
|
128
|
+
except KeyError as e:
|
|
129
|
+
return f"Error: Missing required parameter: {e}"
|
|
130
|
+
except Exception as e:
|
|
131
|
+
return f"Error executing {tool_name}: {str(e)}"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _read_file(file_path: str) -> str:
|
|
135
|
+
"""Read file content."""
|
|
136
|
+
path = Path(file_path)
|
|
137
|
+
|
|
138
|
+
if not path.exists():
|
|
139
|
+
return f"Error: File not found: {file_path}"
|
|
140
|
+
|
|
141
|
+
if not path.is_file():
|
|
142
|
+
return f"Error: Path is not a file: {file_path}"
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
content = path.read_text(encoding="utf-8")
|
|
146
|
+
return f"Successfully read file: {file_path}\n\nContent:\n{content}"
|
|
147
|
+
except UnicodeDecodeError:
|
|
148
|
+
# Try reading as binary and return info
|
|
149
|
+
size = path.stat().st_size
|
|
150
|
+
return f"File {file_path} appears to be binary (size: {size} bytes). Cannot display content."
|
|
151
|
+
except Exception as e:
|
|
152
|
+
return f"Error reading file {file_path}: {str(e)}"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _write_file(file_path: str, content: str) -> str:
|
|
156
|
+
"""Write content to file."""
|
|
157
|
+
path = Path(file_path)
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# Create parent directories if they don't exist
|
|
161
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
|
|
163
|
+
# Write the file
|
|
164
|
+
path.write_text(content, encoding="utf-8")
|
|
165
|
+
|
|
166
|
+
return f"Successfully wrote to file: {file_path} ({len(content)} characters)"
|
|
167
|
+
except Exception as e:
|
|
168
|
+
return f"Error writing to file {file_path}: {str(e)}"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _list_directory(directory_path: str, recursive: bool = False) -> str:
|
|
172
|
+
"""List directory contents."""
|
|
173
|
+
path = Path(directory_path)
|
|
174
|
+
|
|
175
|
+
if not path.exists():
|
|
176
|
+
return f"Error: Directory not found: {directory_path}"
|
|
177
|
+
|
|
178
|
+
if not path.is_dir():
|
|
179
|
+
return f"Error: Path is not a directory: {directory_path}"
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
items = []
|
|
183
|
+
|
|
184
|
+
if recursive:
|
|
185
|
+
# Recursive listing
|
|
186
|
+
for item in path.rglob("*"):
|
|
187
|
+
rel_path = item.relative_to(path)
|
|
188
|
+
item_type = "dir" if item.is_dir() else "file"
|
|
189
|
+
items.append(f"{item_type}: {rel_path}")
|
|
190
|
+
else:
|
|
191
|
+
# Non-recursive listing
|
|
192
|
+
for item in path.iterdir():
|
|
193
|
+
item_type = "dir" if item.is_dir() else "file"
|
|
194
|
+
items.append(f"{item_type}: {item.name}")
|
|
195
|
+
|
|
196
|
+
if not items:
|
|
197
|
+
return f"Directory is empty: {directory_path}"
|
|
198
|
+
|
|
199
|
+
items.sort()
|
|
200
|
+
result = f"Contents of {directory_path}:\n" + "\n".join(items)
|
|
201
|
+
return result
|
|
202
|
+
except Exception as e:
|
|
203
|
+
return f"Error listing directory {directory_path}: {str(e)}"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _file_exists(file_path: str) -> str:
|
|
207
|
+
"""Check if file exists."""
|
|
208
|
+
path = Path(file_path)
|
|
209
|
+
|
|
210
|
+
if path.exists():
|
|
211
|
+
if path.is_file():
|
|
212
|
+
size = path.stat().st_size
|
|
213
|
+
return f"File exists: {file_path} (size: {size} bytes)"
|
|
214
|
+
elif path.is_dir():
|
|
215
|
+
return f"Directory exists: {file_path}"
|
|
216
|
+
else:
|
|
217
|
+
return f"Path exists but is neither file nor directory: {file_path}"
|
|
218
|
+
else:
|
|
219
|
+
return f"Path does not exist: {file_path}"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def create_builtin_tool_executor():
|
|
223
|
+
"""
|
|
224
|
+
Create an executor function for built-in tools.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Executor function that can be passed to create_enhanced_agentic_loop
|
|
228
|
+
"""
|
|
229
|
+
builtin_tool_names = {"read_file", "write_file", "list_directory", "file_exists"}
|
|
230
|
+
|
|
231
|
+
def executor(tool_input: Dict[str, Any]) -> str:
|
|
232
|
+
"""Execute built-in tools."""
|
|
233
|
+
tool_name = tool_input.get("tool_name")
|
|
234
|
+
|
|
235
|
+
if tool_name not in builtin_tool_names:
|
|
236
|
+
raise ValueError(f"Not a built-in tool: {tool_name}")
|
|
237
|
+
|
|
238
|
+
return execute_builtin_file_tool(tool_name, tool_input)
|
|
239
|
+
|
|
240
|
+
return executor
|
skilllite/cli.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for skilllite.
|
|
3
|
+
|
|
4
|
+
Provides commands for managing the skillbox binary, similar to
|
|
5
|
+
how Playwright provides `playwright install` for browser management.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
skilllite install # Download and install the sandbox binary
|
|
9
|
+
skilllite uninstall # Remove the installed binary
|
|
10
|
+
skilllite status # Show installation status
|
|
11
|
+
skilllite version # Show version information
|
|
12
|
+
skilllite mcp # Start MCP server
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
from typing import List, Optional
|
|
18
|
+
|
|
19
|
+
from . import __version__
|
|
20
|
+
from .sandbox.skillbox import (
|
|
21
|
+
BINARY_VERSION,
|
|
22
|
+
get_platform,
|
|
23
|
+
install,
|
|
24
|
+
is_installed,
|
|
25
|
+
get_installed_version,
|
|
26
|
+
uninstall,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def print_status() -> None:
|
|
31
|
+
"""Print installation status."""
|
|
32
|
+
from .sandbox.skillbox import find_binary, get_binary_path
|
|
33
|
+
|
|
34
|
+
print("SkillLite Installation Status")
|
|
35
|
+
print("=" * 40)
|
|
36
|
+
|
|
37
|
+
if is_installed():
|
|
38
|
+
version = get_installed_version()
|
|
39
|
+
print(f"✓ skillbox is installed (v{version})")
|
|
40
|
+
print(f" Location: {get_binary_path()}")
|
|
41
|
+
else:
|
|
42
|
+
binary = find_binary()
|
|
43
|
+
if binary:
|
|
44
|
+
print(f"✓ skillbox found at: {binary}")
|
|
45
|
+
else:
|
|
46
|
+
print("✗ skillbox is not installed")
|
|
47
|
+
print(" Install with: skilllite install")
|
|
48
|
+
|
|
49
|
+
def cmd_install(args: argparse.Namespace) -> int:
|
|
50
|
+
"""Install the skillbox binary."""
|
|
51
|
+
try:
|
|
52
|
+
install(
|
|
53
|
+
version=args.version,
|
|
54
|
+
force=args.force,
|
|
55
|
+
show_progress=not args.quiet
|
|
56
|
+
)
|
|
57
|
+
return 0
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
60
|
+
return 1
|
|
61
|
+
|
|
62
|
+
def cmd_uninstall(args: argparse.Namespace) -> int:
|
|
63
|
+
"""Uninstall the skillbox binary."""
|
|
64
|
+
try:
|
|
65
|
+
uninstall()
|
|
66
|
+
return 0
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
69
|
+
return 1
|
|
70
|
+
|
|
71
|
+
def cmd_status(args: argparse.Namespace) -> int:
|
|
72
|
+
"""Show installation status."""
|
|
73
|
+
try:
|
|
74
|
+
print_status()
|
|
75
|
+
return 0
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
78
|
+
return 1
|
|
79
|
+
|
|
80
|
+
def cmd_version(args: argparse.Namespace) -> int:
|
|
81
|
+
"""Show version information."""
|
|
82
|
+
print(f"skilllite Python SDK: v{__version__}")
|
|
83
|
+
print(f"skillbox binary (bundled): v{BINARY_VERSION}")
|
|
84
|
+
|
|
85
|
+
installed_version = get_installed_version()
|
|
86
|
+
if installed_version:
|
|
87
|
+
print(f"skillbox binary (installed): v{installed_version}")
|
|
88
|
+
else:
|
|
89
|
+
print("skillbox binary (installed): not installed")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
plat = get_platform()
|
|
93
|
+
print(f"Platform: {plat}")
|
|
94
|
+
except RuntimeError as e:
|
|
95
|
+
print(f"Platform: {e}")
|
|
96
|
+
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
def cmd_mcp_server(args: argparse.Namespace) -> int:
|
|
100
|
+
"""Start MCP server."""
|
|
101
|
+
try:
|
|
102
|
+
import asyncio
|
|
103
|
+
from .mcp.server import main as mcp_main
|
|
104
|
+
|
|
105
|
+
asyncio.run(mcp_main())
|
|
106
|
+
return 0
|
|
107
|
+
except ImportError as e:
|
|
108
|
+
print("Error: MCP integration not available", file=sys.stderr)
|
|
109
|
+
print("Please install it with: pip install skilllite[mcp]", file=sys.stderr)
|
|
110
|
+
return 1
|
|
111
|
+
except KeyboardInterrupt:
|
|
112
|
+
print("\nMCP server stopped by user", file=sys.stderr)
|
|
113
|
+
return 0
|
|
114
|
+
except Exception as e:
|
|
115
|
+
import traceback
|
|
116
|
+
print(f"Error starting MCP server: {e}", file=sys.stderr)
|
|
117
|
+
traceback.print_exc()
|
|
118
|
+
return 1
|
|
119
|
+
|
|
120
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
121
|
+
"""Create the argument parser."""
|
|
122
|
+
parser = argparse.ArgumentParser(
|
|
123
|
+
prog="skilllite",
|
|
124
|
+
description="SkillLite - A lightweight Skills execution engine with LLM integration",
|
|
125
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
126
|
+
epilog="""
|
|
127
|
+
Examples:
|
|
128
|
+
skilllite install Install the sandbox binary
|
|
129
|
+
skilllite install --force Force reinstall
|
|
130
|
+
skilllite status Check installation status
|
|
131
|
+
skilllite uninstall Remove the binary
|
|
132
|
+
skilllite mcp Start MCP server (requires pip install skilllite[mcp])
|
|
133
|
+
|
|
134
|
+
For more information, visit: https://github.com/skilllite/skilllite
|
|
135
|
+
"""
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"-V", "--version",
|
|
140
|
+
action="store_true",
|
|
141
|
+
help="Show version information"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
145
|
+
|
|
146
|
+
# install command
|
|
147
|
+
install_parser = subparsers.add_parser(
|
|
148
|
+
"install",
|
|
149
|
+
help="Download and install the skillbox sandbox binary"
|
|
150
|
+
)
|
|
151
|
+
install_parser.add_argument(
|
|
152
|
+
"--version",
|
|
153
|
+
dest="version",
|
|
154
|
+
default=None,
|
|
155
|
+
help=f"Version to install (default: {BINARY_VERSION})"
|
|
156
|
+
)
|
|
157
|
+
install_parser.add_argument(
|
|
158
|
+
"--force", "-f",
|
|
159
|
+
action="store_true",
|
|
160
|
+
help="Force reinstall even if already installed"
|
|
161
|
+
)
|
|
162
|
+
install_parser.add_argument(
|
|
163
|
+
"--quiet", "-q",
|
|
164
|
+
action="store_true",
|
|
165
|
+
help="Suppress progress output"
|
|
166
|
+
)
|
|
167
|
+
install_parser.set_defaults(func=cmd_install)
|
|
168
|
+
|
|
169
|
+
# uninstall command
|
|
170
|
+
uninstall_parser = subparsers.add_parser(
|
|
171
|
+
"uninstall",
|
|
172
|
+
help="Remove the installed skillbox binary"
|
|
173
|
+
)
|
|
174
|
+
uninstall_parser.set_defaults(func=cmd_uninstall)
|
|
175
|
+
|
|
176
|
+
# status command
|
|
177
|
+
status_parser = subparsers.add_parser(
|
|
178
|
+
"status",
|
|
179
|
+
help="Show installation status"
|
|
180
|
+
)
|
|
181
|
+
status_parser.set_defaults(func=cmd_status)
|
|
182
|
+
|
|
183
|
+
# version command (alternative to -V)
|
|
184
|
+
version_parser = subparsers.add_parser(
|
|
185
|
+
"version",
|
|
186
|
+
help="Show version information"
|
|
187
|
+
)
|
|
188
|
+
version_parser.set_defaults(func=cmd_version)
|
|
189
|
+
|
|
190
|
+
# mcp command
|
|
191
|
+
mcp_parser = subparsers.add_parser(
|
|
192
|
+
"mcp",
|
|
193
|
+
help="Start MCP server for SkillLite"
|
|
194
|
+
)
|
|
195
|
+
mcp_parser.set_defaults(func=cmd_mcp_server)
|
|
196
|
+
|
|
197
|
+
return parser
|
|
198
|
+
|
|
199
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
200
|
+
"""Main entry point for the CLI."""
|
|
201
|
+
parser = create_parser()
|
|
202
|
+
args = parser.parse_args(argv)
|
|
203
|
+
|
|
204
|
+
# Handle -V/--version flag
|
|
205
|
+
if args.version:
|
|
206
|
+
return cmd_version(args)
|
|
207
|
+
|
|
208
|
+
# Handle no command
|
|
209
|
+
if not args.command:
|
|
210
|
+
parser.print_help()
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
# Execute the command
|
|
214
|
+
return args.func(args)
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
sys.exit(main())
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SkillLite Core - Core execution engine and data structures.
|
|
3
|
+
|
|
4
|
+
This module contains the essential components that should never be modified
|
|
5
|
+
by external tools or AI assistants without explicit user permission.
|
|
6
|
+
|
|
7
|
+
Core Components:
|
|
8
|
+
- SkillManager: Main interface for managing skills (facade)
|
|
9
|
+
- SkillRegistry: Skill registration and discovery
|
|
10
|
+
- ToolBuilder: Tool definition generation and schema inference
|
|
11
|
+
- PromptBuilder: System prompt context generation
|
|
12
|
+
- ToolCallHandler: LLM response handling and tool execution
|
|
13
|
+
- SkillExecutor: Executes skills using the skillbox binary
|
|
14
|
+
- AgenticLoop: Handles LLM-tool interaction loops
|
|
15
|
+
- SkillInfo: Information container for skills
|
|
16
|
+
- ToolDefinition/ToolResult: Tool protocol adapters
|
|
17
|
+
- ExecutionResult: Result of skill execution
|
|
18
|
+
|
|
19
|
+
These components are protected and should only be modified by:
|
|
20
|
+
1. The original maintainer
|
|
21
|
+
2. Explicit user requests
|
|
22
|
+
3. Critical bug fixes
|
|
23
|
+
|
|
24
|
+
All other functionality (CLI, quick start, utilities, etc.) should be
|
|
25
|
+
implemented in modules outside this core directory.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .manager import SkillManager
|
|
29
|
+
from .executor import SkillExecutor, ExecutionResult
|
|
30
|
+
from .loops import AgenticLoop, AgenticLoopClaudeNative, ApiFormat
|
|
31
|
+
from .skill_info import SkillInfo
|
|
32
|
+
from .tools import ToolDefinition, ToolUseRequest, ToolResult, ToolFormat
|
|
33
|
+
from .metadata import SkillMetadata, NetworkPolicy, parse_skill_metadata
|
|
34
|
+
from .registry import SkillRegistry
|
|
35
|
+
from .tool_builder import ToolBuilder
|
|
36
|
+
from .prompt_builder import PromptBuilder
|
|
37
|
+
from .handler import ToolCallHandler
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Core Management (Facade)
|
|
41
|
+
"SkillManager",
|
|
42
|
+
# Modular Components
|
|
43
|
+
"SkillRegistry",
|
|
44
|
+
"ToolBuilder",
|
|
45
|
+
"PromptBuilder",
|
|
46
|
+
"ToolCallHandler",
|
|
47
|
+
# Skill Info
|
|
48
|
+
"SkillInfo",
|
|
49
|
+
"SkillMetadata",
|
|
50
|
+
"NetworkPolicy",
|
|
51
|
+
# Execution
|
|
52
|
+
"SkillExecutor",
|
|
53
|
+
"ExecutionResult",
|
|
54
|
+
# Loops
|
|
55
|
+
"AgenticLoop",
|
|
56
|
+
"AgenticLoopClaudeNative",
|
|
57
|
+
"ApiFormat",
|
|
58
|
+
# Tools
|
|
59
|
+
"ToolDefinition",
|
|
60
|
+
"ToolUseRequest",
|
|
61
|
+
"ToolResult",
|
|
62
|
+
"ToolFormat",
|
|
63
|
+
# Utilities
|
|
64
|
+
"parse_skill_metadata",
|
|
65
|
+
]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skill executor - interfaces with the Rust skillbox binary.
|
|
3
|
+
|
|
4
|
+
This module provides a thin wrapper around SkillboxExecutor for backward compatibility.
|
|
5
|
+
All actual execution logic is delegated to the sandbox.skillbox.executor module.
|
|
6
|
+
|
|
7
|
+
This is a CORE module - do not modify without explicit permission.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from ..sandbox.base import ExecutionResult
|
|
14
|
+
from ..sandbox.skillbox.executor import SkillboxExecutor
|
|
15
|
+
|
|
16
|
+
__all__ = ['SkillExecutor', 'ExecutionResult']
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SkillExecutor:
|
|
20
|
+
"""
|
|
21
|
+
Executes skills using the skillbox binary.
|
|
22
|
+
|
|
23
|
+
This class provides a Python interface to the Rust-based sandbox executor.
|
|
24
|
+
Supports both traditional skill execution (via entry_point in SKILL.md) and
|
|
25
|
+
direct script execution (via exec command).
|
|
26
|
+
|
|
27
|
+
This is a thin wrapper around SkillboxExecutor for backward compatibility.
|
|
28
|
+
All execution logic is delegated to SkillboxExecutor.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
binary_path: Optional[str] = None,
|
|
34
|
+
cache_dir: Optional[str] = None,
|
|
35
|
+
allow_network: bool = False,
|
|
36
|
+
enable_sandbox: bool = True,
|
|
37
|
+
execution_timeout: Optional[int] = None,
|
|
38
|
+
max_memory_mb: Optional[int] = None,
|
|
39
|
+
sandbox_level: Optional[str] = None,
|
|
40
|
+
auto_install: bool = False
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
Initialize the executor.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
binary_path: Path to the skillbox binary. If None, auto-detect.
|
|
47
|
+
cache_dir: Directory for caching virtual environments.
|
|
48
|
+
allow_network: Whether to allow network access by default.
|
|
49
|
+
enable_sandbox: Whether to enable sandbox protection (default: True).
|
|
50
|
+
execution_timeout: Skill execution timeout in seconds (default: 120).
|
|
51
|
+
max_memory_mb: Maximum memory limit in MB (default: 512).
|
|
52
|
+
sandbox_level: Sandbox security level (1/2/3, default from env or 3).
|
|
53
|
+
auto_install: Automatically download and install binary if not found.
|
|
54
|
+
"""
|
|
55
|
+
self._executor = SkillboxExecutor(
|
|
56
|
+
binary_path=binary_path,
|
|
57
|
+
cache_dir=cache_dir,
|
|
58
|
+
allow_network=allow_network,
|
|
59
|
+
enable_sandbox=enable_sandbox,
|
|
60
|
+
execution_timeout=execution_timeout,
|
|
61
|
+
max_memory_mb=max_memory_mb,
|
|
62
|
+
sandbox_level=sandbox_level,
|
|
63
|
+
auto_install=auto_install
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def binary_path(self) -> str:
|
|
68
|
+
"""Path to the skillbox binary."""
|
|
69
|
+
return self._executor.binary_path
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def cache_dir(self) -> Optional[str]:
|
|
73
|
+
"""Directory for caching virtual environments."""
|
|
74
|
+
return self._executor.cache_dir
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def allow_network(self) -> bool:
|
|
78
|
+
"""Whether network access is allowed by default."""
|
|
79
|
+
return self._executor.allow_network
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def enable_sandbox(self) -> bool:
|
|
83
|
+
"""Whether sandbox protection is enabled."""
|
|
84
|
+
return self._executor.enable_sandbox
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def execution_timeout(self) -> int:
|
|
88
|
+
"""Skill execution timeout in seconds."""
|
|
89
|
+
return self._executor.execution_timeout
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def max_memory_mb(self) -> int:
|
|
93
|
+
"""Maximum memory limit in MB."""
|
|
94
|
+
return self._executor.max_memory_mb
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def sandbox_level(self) -> str:
|
|
98
|
+
"""Sandbox security level (1/2/3)."""
|
|
99
|
+
return self._executor.sandbox_level
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def is_available(self) -> bool:
|
|
103
|
+
"""Check if skillbox is available and ready to use."""
|
|
104
|
+
return self._executor.is_available
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def name(self) -> str:
|
|
108
|
+
"""Return the name of this sandbox implementation."""
|
|
109
|
+
return self._executor.name
|
|
110
|
+
|
|
111
|
+
def execute(
|
|
112
|
+
self,
|
|
113
|
+
skill_dir: Path,
|
|
114
|
+
input_data: Dict[str, Any],
|
|
115
|
+
allow_network: Optional[bool] = None,
|
|
116
|
+
timeout: Optional[int] = None,
|
|
117
|
+
entry_point: Optional[str] = None,
|
|
118
|
+
enable_sandbox: Optional[bool] = None
|
|
119
|
+
) -> ExecutionResult:
|
|
120
|
+
"""
|
|
121
|
+
Execute a skill with the given input.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
skill_dir: Path to the skill directory
|
|
125
|
+
input_data: Input data for the skill
|
|
126
|
+
allow_network: Override default network setting
|
|
127
|
+
timeout: Execution timeout in seconds
|
|
128
|
+
entry_point: Optional specific script to execute (e.g., "scripts/init_skill.py").
|
|
129
|
+
If provided, uses exec_script instead of run command.
|
|
130
|
+
enable_sandbox: Override default sandbox setting
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
ExecutionResult with the output or error
|
|
134
|
+
"""
|
|
135
|
+
return self._executor.execute(
|
|
136
|
+
skill_dir=skill_dir,
|
|
137
|
+
input_data=input_data,
|
|
138
|
+
allow_network=allow_network,
|
|
139
|
+
timeout=timeout,
|
|
140
|
+
entry_point=entry_point,
|
|
141
|
+
enable_sandbox=enable_sandbox
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def exec_script(
|
|
145
|
+
self,
|
|
146
|
+
skill_dir: Path,
|
|
147
|
+
script_path: str,
|
|
148
|
+
input_data: Dict[str, Any],
|
|
149
|
+
args: Optional[list] = None,
|
|
150
|
+
allow_network: Optional[bool] = None,
|
|
151
|
+
timeout: Optional[int] = None,
|
|
152
|
+
enable_sandbox: Optional[bool] = None
|
|
153
|
+
) -> ExecutionResult:
|
|
154
|
+
"""
|
|
155
|
+
Execute a specific script directly.
|
|
156
|
+
|
|
157
|
+
This method allows executing any script in the skill directory without
|
|
158
|
+
requiring an entry_point in SKILL.md. Useful for skills with multiple
|
|
159
|
+
scripts or prompt-only skills with helper scripts.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
skill_dir: Path to the skill directory
|
|
163
|
+
script_path: Relative path to the script (e.g., "scripts/init_skill.py")
|
|
164
|
+
input_data: Input data for the script. For CLI scripts using argparse,
|
|
165
|
+
this will be automatically converted to command line arguments.
|
|
166
|
+
args: Optional command line arguments list (overrides auto-conversion)
|
|
167
|
+
allow_network: Override default network setting
|
|
168
|
+
timeout: Execution timeout in seconds
|
|
169
|
+
enable_sandbox: Override default sandbox setting
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
ExecutionResult with the output or error
|
|
173
|
+
"""
|
|
174
|
+
return self._executor.exec_script(
|
|
175
|
+
skill_dir=skill_dir,
|
|
176
|
+
script_path=script_path,
|
|
177
|
+
input_data=input_data,
|
|
178
|
+
args=args,
|
|
179
|
+
allow_network=allow_network,
|
|
180
|
+
timeout=timeout,
|
|
181
|
+
enable_sandbox=enable_sandbox
|
|
182
|
+
)
|