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
skilllite/__init__.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SkillLite - A lightweight Skills execution engine with LLM integration.
|
|
3
|
+
|
|
4
|
+
This package provides a Python interface to the SkillLite execution engine,
|
|
5
|
+
using OpenAI-compatible API format as the unified interface.
|
|
6
|
+
|
|
7
|
+
Supported LLM providers:
|
|
8
|
+
- OpenAI (GPT-4, GPT-3.5, etc.)
|
|
9
|
+
- Azure OpenAI
|
|
10
|
+
- Anthropic Claude (via OpenAI-compatible endpoint or native)
|
|
11
|
+
- Google Gemini (via OpenAI-compatible endpoint)
|
|
12
|
+
- Local models (Ollama, vLLM, LMStudio, etc.)
|
|
13
|
+
- DeepSeek, Qwen, Moonshot, Zhipu, and other providers
|
|
14
|
+
|
|
15
|
+
Quick Start (Enhanced):
|
|
16
|
+
```python
|
|
17
|
+
from skilllite import SkillRunner
|
|
18
|
+
|
|
19
|
+
# One-line execution with intelligent features
|
|
20
|
+
runner = SkillRunner()
|
|
21
|
+
result = runner.run("Create a data analysis skill for me")
|
|
22
|
+
print(result)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Advanced Usage:
|
|
26
|
+
```python
|
|
27
|
+
from openai import OpenAI
|
|
28
|
+
from skilllite import SkillManager
|
|
29
|
+
|
|
30
|
+
# Works with any OpenAI-compatible client
|
|
31
|
+
client = OpenAI() # or OpenAI(base_url="...", api_key="...")
|
|
32
|
+
manager = SkillManager(skills_dir="./my_skills")
|
|
33
|
+
|
|
34
|
+
# Enhanced agentic loop with task list based execution
|
|
35
|
+
loop = manager.create_enhanced_agentic_loop(
|
|
36
|
+
client=client,
|
|
37
|
+
model="gpt-4"
|
|
38
|
+
)
|
|
39
|
+
response = loop.run("Analyze this data and generate a report")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Legacy Usage:
|
|
43
|
+
```python
|
|
44
|
+
from openai import OpenAI
|
|
45
|
+
from skilllite import SkillManager
|
|
46
|
+
|
|
47
|
+
client = OpenAI()
|
|
48
|
+
manager = SkillManager(skills_dir="./my_skills")
|
|
49
|
+
|
|
50
|
+
# Get tools (OpenAI-compatible format)
|
|
51
|
+
tools = manager.get_tools()
|
|
52
|
+
|
|
53
|
+
# Call any OpenAI-compatible API
|
|
54
|
+
response = client.chat.completions.create(
|
|
55
|
+
model="gpt-4",
|
|
56
|
+
tools=tools,
|
|
57
|
+
messages=[{"role": "user", "content": "..."}]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Handle tool calls
|
|
61
|
+
if response.choices[0].message.tool_calls:
|
|
62
|
+
results = manager.handle_tool_calls(response)
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# Import from core module (protected core functionality)
|
|
67
|
+
from .core import (
|
|
68
|
+
SkillManager,
|
|
69
|
+
SkillInfo,
|
|
70
|
+
AgenticLoop,
|
|
71
|
+
AgenticLoopClaudeNative,
|
|
72
|
+
ApiFormat,
|
|
73
|
+
ToolDefinition,
|
|
74
|
+
ToolUseRequest,
|
|
75
|
+
ToolResult,
|
|
76
|
+
SkillExecutor,
|
|
77
|
+
ExecutionResult,
|
|
78
|
+
SkillMetadata,
|
|
79
|
+
NetworkPolicy,
|
|
80
|
+
parse_skill_metadata,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Import from non-core modules (utilities, quick start, etc.)
|
|
84
|
+
from .quick import SkillRunner, quick_run, load_env, get_runner
|
|
85
|
+
from .core.metadata import get_skill_summary
|
|
86
|
+
from .sandbox.skillbox import (
|
|
87
|
+
install as install_binary,
|
|
88
|
+
uninstall as uninstall_binary,
|
|
89
|
+
is_installed as is_binary_installed,
|
|
90
|
+
find_binary,
|
|
91
|
+
ensure_installed,
|
|
92
|
+
get_installed_version,
|
|
93
|
+
BINARY_VERSION,
|
|
94
|
+
)
|
|
95
|
+
from .analyzer import (
|
|
96
|
+
ScriptAnalyzer,
|
|
97
|
+
ScriptInfo,
|
|
98
|
+
SkillScanResult,
|
|
99
|
+
ExecutionRecommendation,
|
|
100
|
+
scan_skill,
|
|
101
|
+
analyze_skill,
|
|
102
|
+
)
|
|
103
|
+
from .builtin_tools import (
|
|
104
|
+
get_builtin_file_tools,
|
|
105
|
+
execute_builtin_file_tool,
|
|
106
|
+
create_builtin_tool_executor,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Try to import MCP module (optional dependency)
|
|
110
|
+
try:
|
|
111
|
+
from .mcp import MCPServer, SandboxExecutor
|
|
112
|
+
MCP_AVAILABLE = True
|
|
113
|
+
except ImportError:
|
|
114
|
+
MCP_AVAILABLE = False
|
|
115
|
+
|
|
116
|
+
__version__ = "0.1.0"
|
|
117
|
+
__all__ = [
|
|
118
|
+
# Core
|
|
119
|
+
"SkillManager",
|
|
120
|
+
"SkillInfo",
|
|
121
|
+
"AgenticLoop",
|
|
122
|
+
"AgenticLoopClaudeNative",
|
|
123
|
+
"ApiFormat",
|
|
124
|
+
"ToolDefinition",
|
|
125
|
+
"ToolUseRequest",
|
|
126
|
+
"ToolResult",
|
|
127
|
+
"SkillExecutor",
|
|
128
|
+
"ExecutionResult",
|
|
129
|
+
# Script Analysis
|
|
130
|
+
"ScriptAnalyzer",
|
|
131
|
+
"ScriptInfo",
|
|
132
|
+
"SkillScanResult",
|
|
133
|
+
"ExecutionRecommendation",
|
|
134
|
+
"scan_skill",
|
|
135
|
+
"analyze_skill",
|
|
136
|
+
# Schema Inference
|
|
137
|
+
"get_skill_summary",
|
|
138
|
+
# Quick Start
|
|
139
|
+
"SkillRunner",
|
|
140
|
+
"quick_run",
|
|
141
|
+
"load_env",
|
|
142
|
+
"get_runner",
|
|
143
|
+
# Binary Management
|
|
144
|
+
"install_binary",
|
|
145
|
+
"uninstall_binary",
|
|
146
|
+
"is_binary_installed",
|
|
147
|
+
"find_binary",
|
|
148
|
+
"ensure_installed",
|
|
149
|
+
"get_installed_version",
|
|
150
|
+
"BINARY_VERSION",
|
|
151
|
+
# Built-in Tools
|
|
152
|
+
"get_builtin_file_tools",
|
|
153
|
+
"execute_builtin_file_tool",
|
|
154
|
+
"create_builtin_tool_executor",
|
|
155
|
+
# MCP Integration (optional)
|
|
156
|
+
"MCPServer",
|
|
157
|
+
"SandboxExecutor",
|
|
158
|
+
"MCP_AVAILABLE",
|
|
159
|
+
]
|
skilllite/analyzer.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Script Analyzer - Analyzes skill scripts and generates execution recommendations for LLM.
|
|
3
|
+
|
|
4
|
+
This module provides tools to scan skill directories, analyze scripts, and generate
|
|
5
|
+
structured information that can be used by LLMs to decide how to execute skills.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import subprocess
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from .sandbox.skillbox import ensure_installed
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ScriptInfo:
|
|
19
|
+
"""Information about a single script file."""
|
|
20
|
+
path: str
|
|
21
|
+
language: str
|
|
22
|
+
total_lines: int
|
|
23
|
+
preview: str
|
|
24
|
+
description: Optional[str]
|
|
25
|
+
has_main_entry: bool
|
|
26
|
+
uses_argparse: bool
|
|
27
|
+
uses_stdio: bool
|
|
28
|
+
file_size_bytes: int
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ScriptInfo":
|
|
32
|
+
return cls(
|
|
33
|
+
path=data.get("path", ""),
|
|
34
|
+
language=data.get("language", ""),
|
|
35
|
+
total_lines=data.get("total_lines", 0),
|
|
36
|
+
preview=data.get("preview", ""),
|
|
37
|
+
description=data.get("description"),
|
|
38
|
+
has_main_entry=data.get("has_main_entry", False),
|
|
39
|
+
uses_argparse=data.get("uses_argparse", False),
|
|
40
|
+
uses_stdio=data.get("uses_stdio", False),
|
|
41
|
+
file_size_bytes=data.get("file_size_bytes", 0),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class SkillScanResult:
|
|
47
|
+
"""Result of scanning a skill directory."""
|
|
48
|
+
skill_dir: str
|
|
49
|
+
has_skill_md: bool
|
|
50
|
+
skill_metadata: Optional[Dict[str, Any]]
|
|
51
|
+
scripts: List[ScriptInfo]
|
|
52
|
+
directories: Dict[str, bool]
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SkillScanResult":
|
|
56
|
+
scripts = [ScriptInfo.from_dict(s) for s in data.get("scripts", [])]
|
|
57
|
+
return cls(
|
|
58
|
+
skill_dir=data.get("skill_dir", ""),
|
|
59
|
+
has_skill_md=data.get("has_skill_md", False),
|
|
60
|
+
skill_metadata=data.get("skill_metadata"),
|
|
61
|
+
scripts=scripts,
|
|
62
|
+
directories=data.get("directories", {}),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class ExecutionRecommendation:
|
|
68
|
+
"""Recommendation for how to execute a script."""
|
|
69
|
+
script_path: str
|
|
70
|
+
language: str
|
|
71
|
+
execution_method: str # "stdin_json", "argparse", "direct"
|
|
72
|
+
confidence: float # 0.0 to 1.0
|
|
73
|
+
reasoning: str
|
|
74
|
+
suggested_command: str
|
|
75
|
+
input_format: str # "json_stdin", "cli_args", "none"
|
|
76
|
+
output_format: str # "json_stdout", "text_stdout", "file"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ScriptAnalyzer:
|
|
80
|
+
"""
|
|
81
|
+
Analyzes skill directories and scripts to provide execution recommendations.
|
|
82
|
+
|
|
83
|
+
This class uses the skillbox binary to scan directories and then provides
|
|
84
|
+
additional analysis and recommendations for LLM-based execution decisions.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, binary_path: Optional[str] = None, auto_install: bool = False):
|
|
88
|
+
"""
|
|
89
|
+
Initialize the analyzer.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
binary_path: Path to the skillbox binary. If None, auto-detect.
|
|
93
|
+
auto_install: Automatically download and install binary if not found.
|
|
94
|
+
"""
|
|
95
|
+
self.binary_path = binary_path or ensure_installed(auto_install=auto_install)
|
|
96
|
+
|
|
97
|
+
def scan(self, skill_dir: Path, preview_lines: int = 10) -> SkillScanResult:
|
|
98
|
+
"""
|
|
99
|
+
Scan a skill directory and return information about all scripts.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
skill_dir: Path to the skill directory
|
|
103
|
+
preview_lines: Number of lines to include in script preview
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
SkillScanResult with information about the skill and its scripts
|
|
107
|
+
"""
|
|
108
|
+
cmd = [
|
|
109
|
+
self.binary_path,
|
|
110
|
+
"scan",
|
|
111
|
+
str(skill_dir),
|
|
112
|
+
"--preview-lines",
|
|
113
|
+
str(preview_lines),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
117
|
+
|
|
118
|
+
if result.returncode != 0:
|
|
119
|
+
raise RuntimeError(f"Failed to scan skill directory: {result.stderr}")
|
|
120
|
+
|
|
121
|
+
data = json.loads(result.stdout)
|
|
122
|
+
return SkillScanResult.from_dict(data)
|
|
123
|
+
|
|
124
|
+
def analyze_for_execution(
|
|
125
|
+
self,
|
|
126
|
+
skill_dir: Path,
|
|
127
|
+
task_description: Optional[str] = None
|
|
128
|
+
) -> Dict[str, Any]:
|
|
129
|
+
"""
|
|
130
|
+
Analyze a skill directory and generate execution recommendations.
|
|
131
|
+
|
|
132
|
+
This method is designed to produce output that can be directly used
|
|
133
|
+
by an LLM to decide how to execute scripts in the skill.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
skill_dir: Path to the skill directory
|
|
137
|
+
task_description: Optional description of what the user wants to do
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary with analysis results suitable for LLM consumption
|
|
141
|
+
"""
|
|
142
|
+
scan_result = self.scan(skill_dir, preview_lines=15)
|
|
143
|
+
|
|
144
|
+
recommendations = []
|
|
145
|
+
for script in scan_result.scripts:
|
|
146
|
+
rec = self._analyze_script(script, scan_result.skill_metadata)
|
|
147
|
+
recommendations.append(rec)
|
|
148
|
+
|
|
149
|
+
# Sort by confidence
|
|
150
|
+
recommendations.sort(key=lambda r: r.confidence, reverse=True)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"skill_dir": str(skill_dir),
|
|
154
|
+
"skill_name": scan_result.skill_metadata.get("name") if scan_result.skill_metadata else None,
|
|
155
|
+
"skill_description": scan_result.skill_metadata.get("description") if scan_result.skill_metadata else None,
|
|
156
|
+
"has_skill_md": scan_result.has_skill_md,
|
|
157
|
+
"total_scripts": len(scan_result.scripts),
|
|
158
|
+
"directories": scan_result.directories,
|
|
159
|
+
"recommendations": [
|
|
160
|
+
{
|
|
161
|
+
"script_path": r.script_path,
|
|
162
|
+
"language": r.language,
|
|
163
|
+
"execution_method": r.execution_method,
|
|
164
|
+
"confidence": r.confidence,
|
|
165
|
+
"reasoning": r.reasoning,
|
|
166
|
+
"suggested_command": r.suggested_command,
|
|
167
|
+
"input_format": r.input_format,
|
|
168
|
+
"output_format": r.output_format,
|
|
169
|
+
}
|
|
170
|
+
for r in recommendations
|
|
171
|
+
],
|
|
172
|
+
"scripts_detail": [
|
|
173
|
+
{
|
|
174
|
+
"path": s.path,
|
|
175
|
+
"language": s.language,
|
|
176
|
+
"description": s.description,
|
|
177
|
+
"total_lines": s.total_lines,
|
|
178
|
+
"has_main_entry": s.has_main_entry,
|
|
179
|
+
"uses_argparse": s.uses_argparse,
|
|
180
|
+
"uses_stdio": s.uses_stdio,
|
|
181
|
+
}
|
|
182
|
+
for s in scan_result.scripts
|
|
183
|
+
],
|
|
184
|
+
"llm_prompt_hint": self._generate_llm_hint(scan_result, task_description),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
def _analyze_script(
|
|
188
|
+
self,
|
|
189
|
+
script: ScriptInfo,
|
|
190
|
+
skill_metadata: Optional[Dict[str, Any]]
|
|
191
|
+
) -> ExecutionRecommendation:
|
|
192
|
+
"""Analyze a single script and generate execution recommendation."""
|
|
193
|
+
|
|
194
|
+
confidence = 0.5
|
|
195
|
+
reasoning_parts = []
|
|
196
|
+
|
|
197
|
+
# Determine execution method based on script characteristics
|
|
198
|
+
if script.uses_stdio and not script.uses_argparse:
|
|
199
|
+
execution_method = "stdin_json"
|
|
200
|
+
input_format = "json_stdin"
|
|
201
|
+
confidence += 0.3
|
|
202
|
+
reasoning_parts.append("Script uses stdin/stdout for I/O")
|
|
203
|
+
elif script.uses_argparse:
|
|
204
|
+
execution_method = "argparse"
|
|
205
|
+
input_format = "cli_args"
|
|
206
|
+
confidence += 0.2
|
|
207
|
+
reasoning_parts.append("Script uses argument parsing")
|
|
208
|
+
else:
|
|
209
|
+
execution_method = "direct"
|
|
210
|
+
input_format = "none"
|
|
211
|
+
reasoning_parts.append("Script appears to run directly without input")
|
|
212
|
+
|
|
213
|
+
# Boost confidence for scripts with main entry
|
|
214
|
+
if script.has_main_entry:
|
|
215
|
+
confidence += 0.1
|
|
216
|
+
reasoning_parts.append("Has main entry point")
|
|
217
|
+
|
|
218
|
+
# Boost confidence for scripts in scripts/ directory
|
|
219
|
+
if script.path.startswith("scripts/"):
|
|
220
|
+
confidence += 0.1
|
|
221
|
+
reasoning_parts.append("Located in scripts/ directory")
|
|
222
|
+
|
|
223
|
+
# Check if this matches the skill's entry_point
|
|
224
|
+
if skill_metadata and skill_metadata.get("entry_point") == script.path:
|
|
225
|
+
confidence = 1.0
|
|
226
|
+
reasoning_parts.insert(0, "Matches skill entry_point")
|
|
227
|
+
|
|
228
|
+
# Determine output format
|
|
229
|
+
if script.uses_stdio:
|
|
230
|
+
output_format = "json_stdout" if "json" in script.preview.lower() else "text_stdout"
|
|
231
|
+
else:
|
|
232
|
+
output_format = "text_stdout"
|
|
233
|
+
|
|
234
|
+
# Generate suggested command
|
|
235
|
+
suggested_command = self._generate_command(script, execution_method)
|
|
236
|
+
|
|
237
|
+
return ExecutionRecommendation(
|
|
238
|
+
script_path=script.path,
|
|
239
|
+
language=script.language,
|
|
240
|
+
execution_method=execution_method,
|
|
241
|
+
confidence=min(confidence, 1.0),
|
|
242
|
+
reasoning="; ".join(reasoning_parts),
|
|
243
|
+
suggested_command=suggested_command,
|
|
244
|
+
input_format=input_format,
|
|
245
|
+
output_format=output_format,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _generate_command(self, script: ScriptInfo, execution_method: str) -> str:
|
|
249
|
+
"""Generate a suggested command for executing the script."""
|
|
250
|
+
|
|
251
|
+
if execution_method == "stdin_json":
|
|
252
|
+
if script.language == "python":
|
|
253
|
+
return f'echo \'{{"input": "value"}}\' | python {script.path}'
|
|
254
|
+
elif script.language == "node":
|
|
255
|
+
return f'echo \'{{"input": "value"}}\' | node {script.path}'
|
|
256
|
+
elif script.language == "shell":
|
|
257
|
+
return f'echo \'{{"input": "value"}}\' | bash {script.path}'
|
|
258
|
+
elif execution_method == "argparse":
|
|
259
|
+
if script.language == "python":
|
|
260
|
+
return f'python {script.path} --help'
|
|
261
|
+
elif script.language == "node":
|
|
262
|
+
return f'node {script.path} --help'
|
|
263
|
+
elif script.language == "shell":
|
|
264
|
+
return f'bash {script.path} --help'
|
|
265
|
+
else:
|
|
266
|
+
if script.language == "python":
|
|
267
|
+
return f'python {script.path}'
|
|
268
|
+
elif script.language == "node":
|
|
269
|
+
return f'node {script.path}'
|
|
270
|
+
elif script.language == "shell":
|
|
271
|
+
return f'bash {script.path}'
|
|
272
|
+
|
|
273
|
+
return f'# Unknown execution method for {script.path}'
|
|
274
|
+
|
|
275
|
+
def _generate_llm_hint(
|
|
276
|
+
self,
|
|
277
|
+
scan_result: SkillScanResult,
|
|
278
|
+
task_description: Optional[str]
|
|
279
|
+
) -> str:
|
|
280
|
+
"""Generate a hint for LLM to understand how to use this skill."""
|
|
281
|
+
|
|
282
|
+
hints = []
|
|
283
|
+
|
|
284
|
+
if scan_result.skill_metadata:
|
|
285
|
+
meta = scan_result.skill_metadata
|
|
286
|
+
if meta.get("description"):
|
|
287
|
+
hints.append(f"Skill purpose: {meta['description']}")
|
|
288
|
+
if meta.get("entry_point"):
|
|
289
|
+
hints.append(f"Primary entry point: {meta['entry_point']}")
|
|
290
|
+
|
|
291
|
+
if not scan_result.scripts:
|
|
292
|
+
hints.append("No executable scripts found. This may be a prompt-only skill.")
|
|
293
|
+
else:
|
|
294
|
+
script_types = {}
|
|
295
|
+
for s in scan_result.scripts:
|
|
296
|
+
script_types[s.language] = script_types.get(s.language, 0) + 1
|
|
297
|
+
|
|
298
|
+
type_str = ", ".join(f"{count} {lang}" for lang, count in script_types.items())
|
|
299
|
+
hints.append(f"Available scripts: {type_str}")
|
|
300
|
+
|
|
301
|
+
# Highlight scripts with descriptions
|
|
302
|
+
described = [s for s in scan_result.scripts if s.description]
|
|
303
|
+
if described:
|
|
304
|
+
hints.append("Scripts with descriptions:")
|
|
305
|
+
for s in described[:3]: # Limit to 3
|
|
306
|
+
hints.append(f" - {s.path}: {s.description[:100]}")
|
|
307
|
+
|
|
308
|
+
if task_description:
|
|
309
|
+
hints.append(f"User task: {task_description}")
|
|
310
|
+
|
|
311
|
+
return "\n".join(hints)
|
|
312
|
+
|
|
313
|
+
def get_execution_context(self, skill_dir: Path) -> Dict[str, Any]:
|
|
314
|
+
"""
|
|
315
|
+
Get execution context for a skill, suitable for passing to skillbox exec.
|
|
316
|
+
|
|
317
|
+
Returns a dictionary with all information needed to execute scripts
|
|
318
|
+
in the skill directory.
|
|
319
|
+
"""
|
|
320
|
+
scan_result = self.scan(skill_dir)
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
"skill_dir": str(skill_dir.absolute()),
|
|
324
|
+
"has_skill_md": scan_result.has_skill_md,
|
|
325
|
+
"network_enabled": (
|
|
326
|
+
scan_result.skill_metadata.get("network_enabled", False)
|
|
327
|
+
if scan_result.skill_metadata else False
|
|
328
|
+
),
|
|
329
|
+
"compatibility": (
|
|
330
|
+
scan_result.skill_metadata.get("compatibility")
|
|
331
|
+
if scan_result.skill_metadata else None
|
|
332
|
+
),
|
|
333
|
+
"available_scripts": [
|
|
334
|
+
{
|
|
335
|
+
"path": s.path,
|
|
336
|
+
"language": s.language,
|
|
337
|
+
"has_main": s.has_main_entry,
|
|
338
|
+
"uses_argparse": s.uses_argparse,
|
|
339
|
+
"uses_stdio": s.uses_stdio,
|
|
340
|
+
}
|
|
341
|
+
for s in scan_result.scripts
|
|
342
|
+
],
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def scan_skill(skill_dir: str, preview_lines: int = 10) -> Dict[str, Any]:
|
|
347
|
+
"""
|
|
348
|
+
Convenience function to scan a skill directory.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
skill_dir: Path to the skill directory
|
|
352
|
+
preview_lines: Number of lines to include in script preview
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Dictionary with scan results
|
|
356
|
+
"""
|
|
357
|
+
analyzer = ScriptAnalyzer(auto_install=True)
|
|
358
|
+
result = analyzer.scan(Path(skill_dir), preview_lines)
|
|
359
|
+
return {
|
|
360
|
+
"skill_dir": result.skill_dir,
|
|
361
|
+
"has_skill_md": result.has_skill_md,
|
|
362
|
+
"skill_metadata": result.skill_metadata,
|
|
363
|
+
"scripts": [
|
|
364
|
+
{
|
|
365
|
+
"path": s.path,
|
|
366
|
+
"language": s.language,
|
|
367
|
+
"description": s.description,
|
|
368
|
+
"total_lines": s.total_lines,
|
|
369
|
+
"has_main_entry": s.has_main_entry,
|
|
370
|
+
"uses_argparse": s.uses_argparse,
|
|
371
|
+
"uses_stdio": s.uses_stdio,
|
|
372
|
+
}
|
|
373
|
+
for s in result.scripts
|
|
374
|
+
],
|
|
375
|
+
"directories": result.directories,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def analyze_skill(skill_dir: str, task_description: Optional[str] = None) -> Dict[str, Any]:
|
|
380
|
+
"""
|
|
381
|
+
Convenience function to analyze a skill for execution.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
skill_dir: Path to the skill directory
|
|
385
|
+
task_description: Optional description of what the user wants to do
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Dictionary with analysis results and recommendations
|
|
389
|
+
"""
|
|
390
|
+
analyzer = ScriptAnalyzer(auto_install=True)
|
|
391
|
+
return analyzer.analyze_for_execution(Path(skill_dir), task_description)
|