skilllite 0.1.0__py3-none-any.whl → 0.1.2__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 CHANGED
@@ -113,7 +113,7 @@ try:
113
113
  except ImportError:
114
114
  MCP_AVAILABLE = False
115
115
 
116
- __version__ = "0.1.0"
116
+ __version__ = "0.1.1"
117
117
  __all__ = [
118
118
  # Core
119
119
  "SkillManager",
@@ -0,0 +1,19 @@
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
+ skilllite init-opencode # Initialize OpenCode integration
14
+ """
15
+
16
+ from .main import main, create_parser
17
+
18
+ __all__ = ["main", "create_parser"]
19
+
@@ -0,0 +1,10 @@
1
+ """
2
+ Allow running the CLI as a module: python -m skilllite.cli
3
+ """
4
+
5
+ import sys
6
+ from .main import main
7
+
8
+ if __name__ == "__main__":
9
+ sys.exit(main())
10
+
@@ -0,0 +1,93 @@
1
+ """
2
+ Binary management commands for skilllite CLI.
3
+
4
+ Commands: install, uninstall, status, version
5
+ """
6
+
7
+ import argparse
8
+ import sys
9
+
10
+ from .. import __version__
11
+ from ..sandbox.skillbox import (
12
+ BINARY_VERSION,
13
+ get_platform,
14
+ install,
15
+ is_installed,
16
+ get_installed_version,
17
+ uninstall,
18
+ )
19
+
20
+
21
+ def print_status() -> None:
22
+ """Print installation status."""
23
+ from ..sandbox.skillbox import find_binary, get_binary_path
24
+
25
+ print("SkillLite Installation Status")
26
+ print("=" * 40)
27
+
28
+ if is_installed():
29
+ version = get_installed_version()
30
+ print(f"✓ skillbox is installed (v{version})")
31
+ print(f" Location: {get_binary_path()}")
32
+ else:
33
+ binary = find_binary()
34
+ if binary:
35
+ print(f"✓ skillbox found at: {binary}")
36
+ else:
37
+ print("✗ skillbox is not installed")
38
+ print(" Install with: skilllite install")
39
+
40
+
41
+ def cmd_install(args: argparse.Namespace) -> int:
42
+ """Install the skillbox binary."""
43
+ try:
44
+ install(
45
+ version=args.version,
46
+ force=args.force,
47
+ show_progress=not args.quiet
48
+ )
49
+ return 0
50
+ except Exception as e:
51
+ print(f"Error: {e}", file=sys.stderr)
52
+ return 1
53
+
54
+
55
+ def cmd_uninstall(args: argparse.Namespace) -> int:
56
+ """Uninstall the skillbox binary."""
57
+ try:
58
+ uninstall()
59
+ return 0
60
+ except Exception as e:
61
+ print(f"Error: {e}", file=sys.stderr)
62
+ return 1
63
+
64
+
65
+ def cmd_status(args: argparse.Namespace) -> int:
66
+ """Show installation status."""
67
+ try:
68
+ print_status()
69
+ return 0
70
+ except Exception as e:
71
+ print(f"Error: {e}", file=sys.stderr)
72
+ return 1
73
+
74
+
75
+ def cmd_version(args: argparse.Namespace) -> int:
76
+ """Show version information."""
77
+ print(f"skilllite Python SDK: v{__version__}")
78
+ print(f"skillbox binary (bundled): v{BINARY_VERSION}")
79
+
80
+ installed_version = get_installed_version()
81
+ if installed_version:
82
+ print(f"skillbox binary (installed): v{installed_version}")
83
+ else:
84
+ print("skillbox binary (installed): not installed")
85
+
86
+ try:
87
+ plat = get_platform()
88
+ print(f"Platform: {plat}")
89
+ except RuntimeError as e:
90
+ print(f"Platform: {e}")
91
+
92
+ return 0
93
+
@@ -0,0 +1,8 @@
1
+ """
2
+ CLI integrations for external tools.
3
+ """
4
+
5
+ from .opencode import cmd_init_opencode
6
+
7
+ __all__ = ["cmd_init_opencode"]
8
+
@@ -0,0 +1,316 @@
1
+ """
2
+ OpenCode integration for skilllite CLI.
3
+
4
+ Provides the init-opencode command to set up SkillLite integration with OpenCode.
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import List, Dict, Any
13
+
14
+ from ...sandbox.skillbox import (
15
+ install,
16
+ is_installed,
17
+ get_installed_version,
18
+ )
19
+
20
+
21
+ def _get_available_skills(skills_dir: str) -> List[Dict[str, Any]]:
22
+ """Get list of available skills from the skills directory."""
23
+ skills = []
24
+ skills_path = Path(skills_dir)
25
+
26
+ if not skills_path.exists():
27
+ return skills
28
+
29
+ for skill_dir in skills_path.iterdir():
30
+ if not skill_dir.is_dir():
31
+ continue
32
+
33
+ skill_md = skill_dir / "SKILL.md"
34
+ if not skill_md.exists():
35
+ continue
36
+
37
+ # Parse basic skill info from SKILL.md
38
+ try:
39
+ content = skill_md.read_text()
40
+ name = skill_dir.name
41
+ description = ""
42
+
43
+ # Extract description from YAML frontmatter
44
+ if content.startswith("---"):
45
+ parts = content.split("---", 2)
46
+ if len(parts) >= 3:
47
+ import re
48
+ desc_match = re.search(r'description:\s*["\']?([^"\'\n]+)', parts[1])
49
+ if desc_match:
50
+ description = desc_match.group(1).strip()
51
+
52
+ skills.append({
53
+ "name": name,
54
+ "description": description or f"Execute {name} skill"
55
+ })
56
+ except Exception:
57
+ continue
58
+
59
+ return skills
60
+
61
+
62
+ def _generate_skill_md_content(skills: List[Dict[str, Any]]) -> str:
63
+ """Generate the SKILL.md content for OpenCode integration."""
64
+ skills_list = ""
65
+ if skills:
66
+ for skill in skills:
67
+ skills_list += f"- **{skill['name']}**: {skill['description']}\n"
68
+ else:
69
+ skills_list = "- (No pre-defined skills found. Use skilllite_execute_code for code execution.)\n"
70
+
71
+ return f'''---
72
+ name: skilllite
73
+ description: 在安全沙箱中执行代码或预定义技能。当需要运行不信任的代码、网络请求、数据处理时使用。
74
+ ---
75
+
76
+ ## 概述
77
+
78
+ SkillLite 提供了一个安全的沙箱执行环境。代码在系统级沙箱(macOS Seatbelt / Linux Namespace)中隔离运行,防止恶意代码影响主机系统。
79
+
80
+ ## 何时使用 SkillLite 而不是 bash
81
+
82
+ | 场景 | 用 bash | 用 SkillLite |
83
+ |-----|---------|-------------|
84
+ | git 操作 | ✅ | |
85
+ | 读取项目文件 | ✅ | |
86
+ | 执行用户提供的代码 | | ✅ |
87
+ | 网络请求/API 调用 | | ✅ |
88
+ | 数据分析处理 | | ✅ |
89
+ | 运行不信任的脚本 | | ✅ |
90
+ | 执行可能危险的命令 | | ✅ |
91
+
92
+ ## 可用工具
93
+
94
+ ### 1. skilllite_execute_code
95
+ 在沙箱中执行任意代码(Python/JavaScript/Bash)。
96
+
97
+ **参数:**
98
+ - `language`: "python" | "javascript" | "bash"
99
+ - `code`: 要执行的代码
100
+ - `confirmed`: 是否确认执行(高危代码需要)
101
+ - `scan_id`: 扫描 ID(确认执行时需要)
102
+
103
+ **安全确认流程:**
104
+ 当检测到危险代码时,会返回安全报告和 `scan_id`。向用户展示安全问题后,如果用户同意执行,需要再次调用时设置 `confirmed=true` 和返回的 `scan_id`。
105
+
106
+ ### 2. skilllite_run_skill
107
+ 执行预定义技能。
108
+
109
+ **参数:**
110
+ - `skill_name`: 技能名称
111
+ - `input`: 技能的输入参数(JSON 对象)
112
+
113
+ ### 3. skilllite_list_skills
114
+ 查看所有可用的预定义技能。无需参数。
115
+
116
+ ### 4. skilllite_get_skill_info
117
+ 获取指定技能的详细信息,包括输入参数模式。
118
+
119
+ **参数:**
120
+ - `skill_name`: 技能名称
121
+
122
+ ### 5. skilllite_scan_code
123
+ 仅扫描代码安全性,不执行。用于预检查代码是否安全。
124
+
125
+ **参数:**
126
+ - `language`: "python" | "javascript" | "bash"
127
+ - `code`: 要扫描的代码
128
+
129
+ ## 预定义技能
130
+
131
+ {skills_list}
132
+
133
+ ## 使用示例
134
+
135
+ ### 执行 Python 代码
136
+ ```
137
+ skilllite_execute_code(language="python", code="print(sum(range(1, 101)))")
138
+ ```
139
+
140
+ ### 处理危险代码
141
+ 1. 调用 `skilllite_execute_code` 执行代码
142
+ 2. 如果返回 `requires_confirmation=true`,向用户展示安全问题
143
+ 3. 用户确认后,再次调用时带上 `confirmed=true` 和 `scan_id`
144
+
145
+ ### 使用预定义技能
146
+ ```
147
+ skilllite_list_skills() # 查看可用技能
148
+ skilllite_get_skill_info(skill_name="calculator") # 查看技能参数
149
+ skilllite_run_skill(skill_name="calculator", input={{"operation": "add", "a": 5, "b": 3}})
150
+ ```
151
+ '''
152
+
153
+
154
+ def _detect_best_command() -> tuple[List[str], str]:
155
+ """
156
+ Detect the best command to start the MCP server.
157
+
158
+ Returns:
159
+ Tuple of (command_list, description)
160
+
161
+ Priority:
162
+ 1. uvx (if available) - most portable, auto-manages environment
163
+ 2. pipx (if available) - similar to uvx
164
+ 3. python3 -m skilllite.mcp.server - if skilllite is in PATH's python
165
+ 4. Full python path - fallback
166
+ """
167
+ import shutil
168
+ import subprocess
169
+
170
+ # Check if uvx is available
171
+ if shutil.which("uvx"):
172
+ return (["uvx", "skilllite", "mcp"], "uvx (auto-managed)")
173
+
174
+ # Check if pipx is available and skilllite is installed via pipx
175
+ if shutil.which("pipx"):
176
+ # Check if skilllite is installed in pipx
177
+ try:
178
+ result = subprocess.run(
179
+ ["pipx", "list", "--short"],
180
+ capture_output=True,
181
+ text=True,
182
+ timeout=5
183
+ )
184
+ if "skilllite" in result.stdout:
185
+ return (["pipx", "run", "skilllite", "mcp"], "pipx (installed)")
186
+ except Exception:
187
+ pass
188
+
189
+ # Check if skilllite command is directly available in PATH
190
+ if shutil.which("skilllite"):
191
+ return (["skilllite", "mcp"], "skilllite (in PATH)")
192
+
193
+ # Check if python3 has skilllite installed
194
+ python3_path = shutil.which("python3")
195
+ if python3_path:
196
+ try:
197
+ result = subprocess.run(
198
+ [python3_path, "-c", "import skilllite; print('ok')"],
199
+ capture_output=True,
200
+ text=True,
201
+ timeout=5
202
+ )
203
+ if result.returncode == 0 and "ok" in result.stdout:
204
+ return (["python3", "-m", "skilllite.mcp.server"], "python3 (skilllite installed)")
205
+ except Exception:
206
+ pass
207
+
208
+ # Fallback: use current Python's full path
209
+ return ([sys.executable, "-m", "skilllite.mcp.server"], "full path (fallback)")
210
+
211
+
212
+ def _generate_opencode_config(command: List[str], skills_dir: str) -> Dict[str, Any]:
213
+ """Generate OpenCode configuration."""
214
+ return {
215
+ "$schema": "https://opencode.ai/config.json",
216
+ "mcp": {
217
+ "skilllite": {
218
+ "type": "local",
219
+ "command": command,
220
+ "environment": {
221
+ "SKILLBOX_SANDBOX_LEVEL": "3",
222
+ "SKILLLITE_SKILLS_DIR": skills_dir
223
+ },
224
+ "enabled": True
225
+ }
226
+ }
227
+ }
228
+
229
+
230
+ def cmd_init_opencode(args: argparse.Namespace) -> int:
231
+ """Initialize OpenCode integration."""
232
+ try:
233
+ project_dir = Path(args.project_dir or os.getcwd())
234
+ skills_dir = args.skills_dir or "./.skills"
235
+
236
+ print("🚀 Initializing SkillLite integration for OpenCode...")
237
+ print(f" Project directory: {project_dir}")
238
+ print()
239
+
240
+ # 1. Check if skillbox is installed
241
+ if not is_installed():
242
+ print("⚠ skillbox not installed. Installing...")
243
+ install(show_progress=True)
244
+ else:
245
+ version = get_installed_version()
246
+ print(f"✓ skillbox installed (v{version})")
247
+
248
+ # 2. Detect best command to start MCP server
249
+ command, command_desc = _detect_best_command()
250
+ print(f"✓ MCP command: {command_desc}")
251
+ print(f" → {' '.join(command)}")
252
+
253
+ # 3. Create opencode.json
254
+ opencode_config_path = project_dir / "opencode.json"
255
+ config = _generate_opencode_config(command, skills_dir)
256
+
257
+ if opencode_config_path.exists() and not args.force:
258
+ # Merge with existing config
259
+ try:
260
+ existing = json.loads(opencode_config_path.read_text())
261
+ if "mcp" not in existing:
262
+ existing["mcp"] = {}
263
+ existing["mcp"]["skilllite"] = config["mcp"]["skilllite"]
264
+ if "$schema" not in existing:
265
+ existing["$schema"] = config["$schema"]
266
+ config = existing
267
+ print("✓ Updated existing opencode.json")
268
+ except Exception:
269
+ print("⚠ Could not parse existing opencode.json, overwriting")
270
+ else:
271
+ print("✓ Created opencode.json")
272
+
273
+ opencode_config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False))
274
+
275
+ # 4. Get available skills
276
+ # Handle relative path properly - remove leading "./" but keep the rest
277
+ skills_dir_clean = skills_dir[2:] if skills_dir.startswith("./") else skills_dir
278
+ full_skills_dir = project_dir / skills_dir_clean
279
+ skills = _get_available_skills(str(full_skills_dir))
280
+ print(f"✓ Found {len(skills)} skills in {skills_dir}")
281
+
282
+ # 5. Create .opencode/skills/skilllite/SKILL.md
283
+ skill_dir = project_dir / ".opencode" / "skills" / "skilllite"
284
+ skill_dir.mkdir(parents=True, exist_ok=True)
285
+
286
+ skill_md_path = skill_dir / "SKILL.md"
287
+ skill_md_content = _generate_skill_md_content(skills)
288
+ skill_md_path.write_text(skill_md_content, encoding="utf-8")
289
+ print("✓ Created .opencode/skills/skilllite/SKILL.md")
290
+
291
+ # 6. Summary
292
+ print()
293
+ print("=" * 50)
294
+ print("🎉 SkillLite integration initialized successfully!")
295
+ print()
296
+ print("Created files:")
297
+ print(f" • {opencode_config_path.relative_to(project_dir)}")
298
+ print(f" • {skill_md_path.relative_to(project_dir)}")
299
+ print()
300
+ print("Available MCP tools in OpenCode:")
301
+ print(" • skilllite_execute_code - Execute code in sandbox")
302
+ print(" • skilllite_run_skill - Run pre-defined skills")
303
+ print(" • skilllite_list_skills - List available skills")
304
+ print(" • skilllite_get_skill_info - Get skill details")
305
+ print(" • skilllite_scan_code - Scan code for security issues")
306
+ print()
307
+ print("Start OpenCode with: opencode")
308
+ print("=" * 50)
309
+
310
+ return 0
311
+ except Exception as e:
312
+ import traceback
313
+ print(f"Error: {e}", file=sys.stderr)
314
+ traceback.print_exc()
315
+ return 1
316
+
skilllite/cli/main.py ADDED
@@ -0,0 +1,142 @@
1
+ """
2
+ Main entry point for skilllite CLI.
3
+
4
+ Provides the argument parser and main function.
5
+ """
6
+
7
+ import argparse
8
+ import sys
9
+ from typing import List, Optional
10
+
11
+ from ..sandbox.skillbox import BINARY_VERSION
12
+ from .binary import cmd_install, cmd_uninstall, cmd_status, cmd_version
13
+ from .mcp import cmd_mcp_server
14
+ from .integrations.opencode import cmd_init_opencode
15
+
16
+
17
+ def create_parser() -> argparse.ArgumentParser:
18
+ """Create the argument parser."""
19
+ parser = argparse.ArgumentParser(
20
+ prog="skilllite",
21
+ description="SkillLite - A lightweight Skills execution engine with LLM integration",
22
+ formatter_class=argparse.RawDescriptionHelpFormatter,
23
+ epilog="""
24
+ Examples:
25
+ skilllite install Install the sandbox binary
26
+ skilllite install --force Force reinstall
27
+ skilllite status Check installation status
28
+ skilllite uninstall Remove the binary
29
+ skilllite mcp Start MCP server (requires pip install skilllite[mcp])
30
+ skilllite init-opencode Initialize OpenCode integration
31
+
32
+ For more information, visit: https://github.com/skilllite/skilllite
33
+ """
34
+ )
35
+
36
+ parser.add_argument(
37
+ "-V", "--version",
38
+ action="store_true",
39
+ help="Show version information"
40
+ )
41
+
42
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
43
+
44
+ # install command
45
+ install_parser = subparsers.add_parser(
46
+ "install",
47
+ help="Download and install the skillbox sandbox binary"
48
+ )
49
+ install_parser.add_argument(
50
+ "--version",
51
+ dest="version",
52
+ default=None,
53
+ help=f"Version to install (default: {BINARY_VERSION})"
54
+ )
55
+ install_parser.add_argument(
56
+ "--force", "-f",
57
+ action="store_true",
58
+ help="Force reinstall even if already installed"
59
+ )
60
+ install_parser.add_argument(
61
+ "--quiet", "-q",
62
+ action="store_true",
63
+ help="Suppress progress output"
64
+ )
65
+ install_parser.set_defaults(func=cmd_install)
66
+
67
+ # uninstall command
68
+ uninstall_parser = subparsers.add_parser(
69
+ "uninstall",
70
+ help="Remove the installed skillbox binary"
71
+ )
72
+ uninstall_parser.set_defaults(func=cmd_uninstall)
73
+
74
+ # status command
75
+ status_parser = subparsers.add_parser(
76
+ "status",
77
+ help="Show installation status"
78
+ )
79
+ status_parser.set_defaults(func=cmd_status)
80
+
81
+ # version command (alternative to -V)
82
+ version_parser = subparsers.add_parser(
83
+ "version",
84
+ help="Show version information"
85
+ )
86
+ version_parser.set_defaults(func=cmd_version)
87
+
88
+ # mcp command
89
+ mcp_parser = subparsers.add_parser(
90
+ "mcp",
91
+ help="Start MCP server for SkillLite"
92
+ )
93
+ mcp_parser.set_defaults(func=cmd_mcp_server)
94
+
95
+ # init-opencode command
96
+ init_opencode_parser = subparsers.add_parser(
97
+ "init-opencode",
98
+ help="Initialize SkillLite integration for OpenCode"
99
+ )
100
+ init_opencode_parser.add_argument(
101
+ "--project-dir", "-p",
102
+ dest="project_dir",
103
+ default=None,
104
+ help="Project directory (default: current directory)"
105
+ )
106
+ init_opencode_parser.add_argument(
107
+ "--skills-dir", "-s",
108
+ dest="skills_dir",
109
+ default="./.skills",
110
+ help="Skills directory path (default: ./.skills)"
111
+ )
112
+ init_opencode_parser.add_argument(
113
+ "--force", "-f",
114
+ action="store_true",
115
+ help="Force overwrite existing opencode.json"
116
+ )
117
+ init_opencode_parser.set_defaults(func=cmd_init_opencode)
118
+
119
+ return parser
120
+
121
+
122
+ def main(argv: Optional[List[str]] = None) -> int:
123
+ """Main entry point for the CLI."""
124
+ parser = create_parser()
125
+ args = parser.parse_args(argv)
126
+
127
+ # Handle -V/--version flag
128
+ if args.version:
129
+ return cmd_version(args)
130
+
131
+ # Handle no command
132
+ if not args.command:
133
+ parser.print_help()
134
+ return 0
135
+
136
+ # Execute the command
137
+ return args.func(args)
138
+
139
+
140
+ if __name__ == "__main__":
141
+ sys.exit(main())
142
+
skilllite/cli/mcp.py ADDED
@@ -0,0 +1,29 @@
1
+ """
2
+ MCP server command for skilllite CLI.
3
+ """
4
+
5
+ import argparse
6
+ import sys
7
+
8
+
9
+ def cmd_mcp_server(args: argparse.Namespace) -> int:
10
+ """Start MCP server."""
11
+ try:
12
+ import asyncio
13
+ from ..mcp.server import main as mcp_main
14
+
15
+ asyncio.run(mcp_main())
16
+ return 0
17
+ except ImportError as e:
18
+ print("Error: MCP integration not available", file=sys.stderr)
19
+ print("Please install it with: pip install skilllite[mcp]", file=sys.stderr)
20
+ return 1
21
+ except KeyboardInterrupt:
22
+ print("\nMCP server stopped by user", file=sys.stderr)
23
+ return 0
24
+ except Exception as e:
25
+ import traceback
26
+ print(f"Error starting MCP server: {e}", file=sys.stderr)
27
+ traceback.print_exc()
28
+ return 1
29
+
@@ -62,4 +62,6 @@ __all__ = [
62
62
  "ToolFormat",
63
63
  # Utilities
64
64
  "parse_skill_metadata",
65
+ # Adapters (lazy loaded)
66
+ "adapters",
65
67
  ]