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.
@@ -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
+ )