skill-seekers 2.7.3__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.
- skill_seekers/__init__.py +22 -0
- skill_seekers/cli/__init__.py +39 -0
- skill_seekers/cli/adaptors/__init__.py +120 -0
- skill_seekers/cli/adaptors/base.py +221 -0
- skill_seekers/cli/adaptors/claude.py +485 -0
- skill_seekers/cli/adaptors/gemini.py +453 -0
- skill_seekers/cli/adaptors/markdown.py +269 -0
- skill_seekers/cli/adaptors/openai.py +503 -0
- skill_seekers/cli/ai_enhancer.py +310 -0
- skill_seekers/cli/api_reference_builder.py +373 -0
- skill_seekers/cli/architectural_pattern_detector.py +525 -0
- skill_seekers/cli/code_analyzer.py +1462 -0
- skill_seekers/cli/codebase_scraper.py +1225 -0
- skill_seekers/cli/config_command.py +563 -0
- skill_seekers/cli/config_enhancer.py +431 -0
- skill_seekers/cli/config_extractor.py +871 -0
- skill_seekers/cli/config_manager.py +452 -0
- skill_seekers/cli/config_validator.py +394 -0
- skill_seekers/cli/conflict_detector.py +528 -0
- skill_seekers/cli/constants.py +72 -0
- skill_seekers/cli/dependency_analyzer.py +757 -0
- skill_seekers/cli/doc_scraper.py +2332 -0
- skill_seekers/cli/enhance_skill.py +488 -0
- skill_seekers/cli/enhance_skill_local.py +1096 -0
- skill_seekers/cli/enhance_status.py +194 -0
- skill_seekers/cli/estimate_pages.py +433 -0
- skill_seekers/cli/generate_router.py +1209 -0
- skill_seekers/cli/github_fetcher.py +534 -0
- skill_seekers/cli/github_scraper.py +1466 -0
- skill_seekers/cli/guide_enhancer.py +723 -0
- skill_seekers/cli/how_to_guide_builder.py +1267 -0
- skill_seekers/cli/install_agent.py +461 -0
- skill_seekers/cli/install_skill.py +178 -0
- skill_seekers/cli/language_detector.py +614 -0
- skill_seekers/cli/llms_txt_detector.py +60 -0
- skill_seekers/cli/llms_txt_downloader.py +104 -0
- skill_seekers/cli/llms_txt_parser.py +150 -0
- skill_seekers/cli/main.py +558 -0
- skill_seekers/cli/markdown_cleaner.py +132 -0
- skill_seekers/cli/merge_sources.py +806 -0
- skill_seekers/cli/package_multi.py +77 -0
- skill_seekers/cli/package_skill.py +241 -0
- skill_seekers/cli/pattern_recognizer.py +1825 -0
- skill_seekers/cli/pdf_extractor_poc.py +1166 -0
- skill_seekers/cli/pdf_scraper.py +617 -0
- skill_seekers/cli/quality_checker.py +519 -0
- skill_seekers/cli/rate_limit_handler.py +438 -0
- skill_seekers/cli/resume_command.py +160 -0
- skill_seekers/cli/run_tests.py +230 -0
- skill_seekers/cli/setup_wizard.py +93 -0
- skill_seekers/cli/split_config.py +390 -0
- skill_seekers/cli/swift_patterns.py +560 -0
- skill_seekers/cli/test_example_extractor.py +1081 -0
- skill_seekers/cli/test_unified_simple.py +179 -0
- skill_seekers/cli/unified_codebase_analyzer.py +572 -0
- skill_seekers/cli/unified_scraper.py +932 -0
- skill_seekers/cli/unified_skill_builder.py +1605 -0
- skill_seekers/cli/upload_skill.py +162 -0
- skill_seekers/cli/utils.py +432 -0
- skill_seekers/mcp/__init__.py +33 -0
- skill_seekers/mcp/agent_detector.py +316 -0
- skill_seekers/mcp/git_repo.py +273 -0
- skill_seekers/mcp/server.py +231 -0
- skill_seekers/mcp/server_fastmcp.py +1249 -0
- skill_seekers/mcp/server_legacy.py +2302 -0
- skill_seekers/mcp/source_manager.py +285 -0
- skill_seekers/mcp/tools/__init__.py +115 -0
- skill_seekers/mcp/tools/config_tools.py +251 -0
- skill_seekers/mcp/tools/packaging_tools.py +826 -0
- skill_seekers/mcp/tools/scraping_tools.py +842 -0
- skill_seekers/mcp/tools/source_tools.py +828 -0
- skill_seekers/mcp/tools/splitting_tools.py +212 -0
- skill_seekers/py.typed +0 -0
- skill_seekers-2.7.3.dist-info/METADATA +2027 -0
- skill_seekers-2.7.3.dist-info/RECORD +79 -0
- skill_seekers-2.7.3.dist-info/WHEEL +5 -0
- skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
- skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
- skill_seekers-2.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Install skills to AI coding agent directories.
|
|
4
|
+
|
|
5
|
+
This module provides functionality to install Skill Seekers-generated skills
|
|
6
|
+
to various AI coding agents (Claude Code, Cursor, VS Code, Amp, Goose, etc.)
|
|
7
|
+
by copying skill directories to agent-specific installation paths.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
skill-seekers install-agent <skill_directory> --agent <agent_name> [--force] [--dry-run]
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
# Install to specific agent
|
|
14
|
+
skill-seekers install-agent output/react/ --agent cursor
|
|
15
|
+
|
|
16
|
+
# Install to all agents at once
|
|
17
|
+
skill-seekers install-agent output/react/ --agent all
|
|
18
|
+
|
|
19
|
+
# Force overwrite existing installation
|
|
20
|
+
skill-seekers install-agent output/react/ --agent claude --force
|
|
21
|
+
|
|
22
|
+
# Preview installation without making changes
|
|
23
|
+
skill-seekers install-agent output/react/ --agent cursor --dry-run
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import shutil
|
|
28
|
+
import sys
|
|
29
|
+
from difflib import get_close_matches
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
# Agent installation paths
|
|
33
|
+
# Global paths (install to home directory): Use ~/.{agent}/skills/
|
|
34
|
+
# Project paths (install to current directory): Use .{agent}/skills/
|
|
35
|
+
AGENT_PATHS = {
|
|
36
|
+
"claude": "~/.claude/skills/", # Global (home)
|
|
37
|
+
"cursor": ".cursor/skills/", # Project-relative
|
|
38
|
+
"vscode": ".github/skills/", # Project-relative
|
|
39
|
+
"copilot": ".github/skills/", # Same as VSCode
|
|
40
|
+
"amp": "~/.amp/skills/", # Global
|
|
41
|
+
"goose": "~/.config/goose/skills/", # Global
|
|
42
|
+
"opencode": "~/.opencode/skills/", # Global
|
|
43
|
+
"letta": "~/.letta/skills/", # Global
|
|
44
|
+
"aide": "~/.aide/skills/", # Global
|
|
45
|
+
"windsurf": "~/.windsurf/skills/", # Global
|
|
46
|
+
"neovate": "~/.neovate/skills/", # Global
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_agent_path(agent_name: str, project_root: Path | None = None) -> Path:
|
|
51
|
+
"""
|
|
52
|
+
Resolve the installation path for a given agent.
|
|
53
|
+
|
|
54
|
+
Handles both global paths (~/.<agent>/skills/) and project-relative paths
|
|
55
|
+
(.cursor/skills/, .github/skills/).
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
agent_name: Name of the agent (e.g., 'claude', 'cursor')
|
|
59
|
+
project_root: Optional project root directory for project-relative paths
|
|
60
|
+
(defaults to current working directory)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Absolute path to the agent's skill installation directory
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ValueError: If agent_name is not recognized
|
|
67
|
+
"""
|
|
68
|
+
agent_name = agent_name.lower()
|
|
69
|
+
|
|
70
|
+
if agent_name not in AGENT_PATHS:
|
|
71
|
+
raise ValueError(f"Unknown agent: {agent_name}")
|
|
72
|
+
|
|
73
|
+
path_template = AGENT_PATHS[agent_name]
|
|
74
|
+
|
|
75
|
+
# Handle home directory expansion (~)
|
|
76
|
+
if path_template.startswith("~"):
|
|
77
|
+
return Path(path_template).expanduser()
|
|
78
|
+
|
|
79
|
+
# Handle project-relative paths
|
|
80
|
+
if project_root is None:
|
|
81
|
+
project_root = Path.cwd()
|
|
82
|
+
|
|
83
|
+
return project_root / path_template
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_available_agents() -> list:
|
|
87
|
+
"""
|
|
88
|
+
Get list of all supported agent names.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of agent names (lowercase)
|
|
92
|
+
"""
|
|
93
|
+
return sorted(AGENT_PATHS.keys())
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def validate_agent_name(agent_name: str) -> tuple[bool, str | None]:
|
|
97
|
+
"""
|
|
98
|
+
Validate an agent name and provide suggestions if invalid.
|
|
99
|
+
|
|
100
|
+
Performs case-insensitive matching and fuzzy matching to suggest
|
|
101
|
+
similar agent names if the provided name is invalid.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
agent_name: Agent name to validate
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Tuple of (is_valid, error_message)
|
|
108
|
+
- is_valid: True if agent name is valid, False otherwise
|
|
109
|
+
- error_message: None if valid, error message with suggestions if invalid
|
|
110
|
+
"""
|
|
111
|
+
# Special case: 'all' is valid for installing to all agents
|
|
112
|
+
if agent_name.lower() == "all":
|
|
113
|
+
return True, None
|
|
114
|
+
|
|
115
|
+
# Case-insensitive check
|
|
116
|
+
if agent_name.lower() in AGENT_PATHS:
|
|
117
|
+
return True, None
|
|
118
|
+
|
|
119
|
+
# Agent not found - provide suggestions
|
|
120
|
+
available = get_available_agents()
|
|
121
|
+
|
|
122
|
+
# Try fuzzy matching (find similar names)
|
|
123
|
+
suggestions = get_close_matches(agent_name.lower(), available, n=1, cutoff=0.6)
|
|
124
|
+
|
|
125
|
+
error_msg = f"Unknown agent '{agent_name}'\n\n"
|
|
126
|
+
|
|
127
|
+
if suggestions:
|
|
128
|
+
error_msg += f"Did you mean: {suggestions[0]}?\n\n"
|
|
129
|
+
|
|
130
|
+
error_msg += "Available agents:\n "
|
|
131
|
+
error_msg += ", ".join(available + ["all"])
|
|
132
|
+
error_msg += f"\n\nUsage:\n skill-seekers install-agent <skill_directory> --agent {suggestions[0] if suggestions else 'claude'}"
|
|
133
|
+
|
|
134
|
+
return False, error_msg
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def validate_skill_directory(skill_dir: Path) -> tuple[bool, str | None]:
|
|
138
|
+
"""
|
|
139
|
+
Validate that a directory is a valid skill directory.
|
|
140
|
+
|
|
141
|
+
A valid skill directory must:
|
|
142
|
+
- Exist
|
|
143
|
+
- Be a directory
|
|
144
|
+
- Contain a SKILL.md file
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
skill_dir: Path to skill directory
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Tuple of (is_valid, error_message)
|
|
151
|
+
"""
|
|
152
|
+
if not skill_dir.exists():
|
|
153
|
+
return False, f"Skill directory does not exist: {skill_dir}"
|
|
154
|
+
|
|
155
|
+
if not skill_dir.is_dir():
|
|
156
|
+
return False, f"Path is not a directory: {skill_dir}"
|
|
157
|
+
|
|
158
|
+
skill_md = skill_dir / "SKILL.md"
|
|
159
|
+
if not skill_md.exists():
|
|
160
|
+
return False, f"SKILL.md not found in {skill_dir}"
|
|
161
|
+
|
|
162
|
+
return True, None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def install_to_agent(
|
|
166
|
+
skill_dir: str | Path, agent_name: str, force: bool = False, dry_run: bool = False
|
|
167
|
+
) -> tuple[bool, str]:
|
|
168
|
+
"""
|
|
169
|
+
Install a skill to a specific agent's directory.
|
|
170
|
+
|
|
171
|
+
Copies the skill directory to the agent's installation path, excluding
|
|
172
|
+
backup files and temporary files.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
skill_dir: Path to skill directory
|
|
176
|
+
agent_name: Name of agent to install to
|
|
177
|
+
force: If True, overwrite existing installation without asking
|
|
178
|
+
dry_run: If True, preview installation without making changes
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Tuple of (success, message)
|
|
182
|
+
- success: True if installation succeeded, False otherwise
|
|
183
|
+
- message: Success message or error description
|
|
184
|
+
"""
|
|
185
|
+
# Convert to Path
|
|
186
|
+
skill_dir = Path(skill_dir).resolve()
|
|
187
|
+
skill_name = skill_dir.name
|
|
188
|
+
|
|
189
|
+
# Validate skill directory
|
|
190
|
+
is_valid, error_msg = validate_skill_directory(skill_dir)
|
|
191
|
+
if not is_valid:
|
|
192
|
+
return False, f"❌ {error_msg}"
|
|
193
|
+
|
|
194
|
+
# Validate agent name
|
|
195
|
+
is_valid, error_msg = validate_agent_name(agent_name)
|
|
196
|
+
if not is_valid:
|
|
197
|
+
return False, f"❌ {error_msg}"
|
|
198
|
+
|
|
199
|
+
# Get agent installation path
|
|
200
|
+
try:
|
|
201
|
+
agent_base_path = get_agent_path(agent_name.lower())
|
|
202
|
+
except ValueError as e:
|
|
203
|
+
return False, f"❌ {str(e)}"
|
|
204
|
+
|
|
205
|
+
# Target path: {agent_base_path}/{skill_name}/
|
|
206
|
+
target_path = agent_base_path / skill_name
|
|
207
|
+
|
|
208
|
+
# Check if already exists
|
|
209
|
+
if target_path.exists() and not force:
|
|
210
|
+
error_msg = "❌ Skill already installed\n\n"
|
|
211
|
+
error_msg += f"Location: {target_path}\n\n"
|
|
212
|
+
error_msg += "Options:\n"
|
|
213
|
+
error_msg += f" 1. Overwrite: skill-seekers install-agent {skill_dir} --agent {agent_name} --force\n"
|
|
214
|
+
error_msg += f" 2. Remove: rm -rf {target_path}\n"
|
|
215
|
+
error_msg += f" 3. Rename: mv {skill_dir} {skill_dir.parent / (skill_name + '-v2')}"
|
|
216
|
+
return False, error_msg
|
|
217
|
+
|
|
218
|
+
# Dry run mode - just preview
|
|
219
|
+
if dry_run:
|
|
220
|
+
msg = "🔍 DRY RUN - No changes will be made\n\n"
|
|
221
|
+
msg += f"Would install skill: {skill_name}\n"
|
|
222
|
+
msg += f" Source: {skill_dir}\n"
|
|
223
|
+
msg += f" Target: {target_path}\n\n"
|
|
224
|
+
|
|
225
|
+
# Calculate total size
|
|
226
|
+
total_size = sum(f.stat().st_size for f in skill_dir.rglob("*") if f.is_file())
|
|
227
|
+
|
|
228
|
+
msg += "Files to copy:\n"
|
|
229
|
+
msg += f" SKILL.md ({(skill_dir / 'SKILL.md').stat().st_size / 1024:.1f} KB)\n"
|
|
230
|
+
|
|
231
|
+
references_dir = skill_dir / "references"
|
|
232
|
+
if references_dir.exists():
|
|
233
|
+
ref_files = list(references_dir.rglob("*.md"))
|
|
234
|
+
ref_size = sum(f.stat().st_size for f in ref_files)
|
|
235
|
+
msg += f" references/ ({len(ref_files)} files, {ref_size / 1024:.1f} KB)\n"
|
|
236
|
+
|
|
237
|
+
for subdir in ["scripts", "assets"]:
|
|
238
|
+
subdir_path = skill_dir / subdir
|
|
239
|
+
if subdir_path.exists():
|
|
240
|
+
files = list(subdir_path.rglob("*"))
|
|
241
|
+
if files:
|
|
242
|
+
msg += f" {subdir}/ ({len(files)} files)\n"
|
|
243
|
+
else:
|
|
244
|
+
msg += f" {subdir}/ (empty)\n"
|
|
245
|
+
|
|
246
|
+
msg += f"\nTotal size: {total_size / 1024:.1f} KB\n\n"
|
|
247
|
+
msg += "To actually install, run:\n"
|
|
248
|
+
msg += f" skill-seekers install-agent {skill_dir} --agent {agent_name}"
|
|
249
|
+
|
|
250
|
+
return True, msg
|
|
251
|
+
|
|
252
|
+
# Create parent directories if needed
|
|
253
|
+
try:
|
|
254
|
+
agent_base_path.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
except PermissionError:
|
|
256
|
+
return (
|
|
257
|
+
False,
|
|
258
|
+
f"❌ Permission denied: {agent_base_path}\n\nTry: sudo mkdir -p {agent_base_path} && sudo chown -R $USER {agent_base_path}",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Copy skill directory
|
|
262
|
+
def ignore_files(_directory, files):
|
|
263
|
+
"""Filter function for shutil.copytree to exclude unwanted files."""
|
|
264
|
+
ignored = []
|
|
265
|
+
for f in files:
|
|
266
|
+
# Exclude backup files
|
|
267
|
+
if (
|
|
268
|
+
f.endswith(".backup")
|
|
269
|
+
or f == "__pycache__"
|
|
270
|
+
or f == ".DS_Store"
|
|
271
|
+
or f.startswith(".")
|
|
272
|
+
and f not in [".github", ".cursor"]
|
|
273
|
+
):
|
|
274
|
+
ignored.append(f)
|
|
275
|
+
return ignored
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
# Remove existing if force mode
|
|
279
|
+
if target_path.exists() and force:
|
|
280
|
+
shutil.rmtree(target_path)
|
|
281
|
+
|
|
282
|
+
# Copy directory
|
|
283
|
+
shutil.copytree(skill_dir, target_path, ignore=ignore_files)
|
|
284
|
+
|
|
285
|
+
# Success message
|
|
286
|
+
msg = "✅ Installation complete!\n\n"
|
|
287
|
+
msg += f"Skill '{skill_name}' installed to {agent_name}\n"
|
|
288
|
+
msg += f"Location: {target_path}\n\n"
|
|
289
|
+
|
|
290
|
+
# Agent-specific restart instructions
|
|
291
|
+
if agent_name.lower() == "claude":
|
|
292
|
+
msg += "Restart Claude Code to load the new skill."
|
|
293
|
+
elif agent_name.lower() == "cursor":
|
|
294
|
+
msg += "Restart Cursor to load the new skill."
|
|
295
|
+
elif agent_name.lower() in ["vscode", "copilot"]:
|
|
296
|
+
msg += "Restart VS Code to load the new skill."
|
|
297
|
+
else:
|
|
298
|
+
msg += f"Restart {agent_name.capitalize()} to load the new skill."
|
|
299
|
+
|
|
300
|
+
return True, msg
|
|
301
|
+
|
|
302
|
+
except PermissionError as e:
|
|
303
|
+
return (
|
|
304
|
+
False,
|
|
305
|
+
f"❌ Permission denied: {e}\n\nTry: sudo mkdir -p {agent_base_path} && sudo chown -R $USER {agent_base_path}",
|
|
306
|
+
)
|
|
307
|
+
except Exception as e:
|
|
308
|
+
return False, f"❌ Installation failed: {e}"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def install_to_all_agents(
|
|
312
|
+
skill_dir: str | Path, force: bool = False, dry_run: bool = False
|
|
313
|
+
) -> dict[str, tuple[bool, str]]:
|
|
314
|
+
"""
|
|
315
|
+
Install a skill to all available agents.
|
|
316
|
+
|
|
317
|
+
Attempts to install the skill to all agents in AGENT_PATHS,
|
|
318
|
+
collecting results for each agent.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
skill_dir: Path to skill directory
|
|
322
|
+
force: If True, overwrite existing installations
|
|
323
|
+
dry_run: If True, preview installations without making changes
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Dictionary mapping agent names to (success, message) tuples
|
|
327
|
+
"""
|
|
328
|
+
results = {}
|
|
329
|
+
|
|
330
|
+
for agent_name in get_available_agents():
|
|
331
|
+
success, message = install_to_agent(skill_dir, agent_name, force=force, dry_run=dry_run)
|
|
332
|
+
results[agent_name] = (success, message)
|
|
333
|
+
|
|
334
|
+
return results
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def main() -> int:
|
|
338
|
+
"""
|
|
339
|
+
Main entry point for install-agent CLI.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Exit code (0 for success, 1 for error)
|
|
343
|
+
"""
|
|
344
|
+
parser = argparse.ArgumentParser(
|
|
345
|
+
prog="skill-seekers-install-agent",
|
|
346
|
+
description="Install skills to AI coding agent directories",
|
|
347
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
348
|
+
epilog="""
|
|
349
|
+
Examples:
|
|
350
|
+
# Install to specific agent
|
|
351
|
+
skill-seekers install-agent output/react/ --agent cursor
|
|
352
|
+
|
|
353
|
+
# Install to all agents
|
|
354
|
+
skill-seekers install-agent output/react/ --agent all
|
|
355
|
+
|
|
356
|
+
# Force overwrite
|
|
357
|
+
skill-seekers install-agent output/react/ --agent claude --force
|
|
358
|
+
|
|
359
|
+
# Preview installation
|
|
360
|
+
skill-seekers install-agent output/react/ --agent cursor --dry-run
|
|
361
|
+
|
|
362
|
+
Supported agents:
|
|
363
|
+
claude, cursor, vscode, copilot, amp, goose, opencode, letta, aide, windsurf, neovate, all
|
|
364
|
+
""",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
parser.add_argument("skill_directory", help="Path to skill directory (e.g., output/react/)")
|
|
368
|
+
|
|
369
|
+
parser.add_argument(
|
|
370
|
+
"--agent", required=True, help="Agent name (use 'all' to install to all agents)"
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
parser.add_argument(
|
|
374
|
+
"--force", action="store_true", help="Overwrite existing installation without asking"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
parser.add_argument(
|
|
378
|
+
"--dry-run", action="store_true", help="Preview installation without making changes"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
args = parser.parse_args()
|
|
382
|
+
|
|
383
|
+
# Convert skill directory to Path
|
|
384
|
+
skill_dir = Path(args.skill_directory)
|
|
385
|
+
skill_name = skill_dir.name
|
|
386
|
+
|
|
387
|
+
# Handle 'all' agent
|
|
388
|
+
if args.agent.lower() == "all":
|
|
389
|
+
print(f"\n📋 Installing skill to all agents: {skill_name}\n")
|
|
390
|
+
|
|
391
|
+
if args.dry_run:
|
|
392
|
+
print("🔍 DRY RUN MODE - No changes will be made\n")
|
|
393
|
+
|
|
394
|
+
results = install_to_all_agents(skill_dir, force=args.force, dry_run=args.dry_run)
|
|
395
|
+
|
|
396
|
+
# Print results
|
|
397
|
+
installed_count = 0
|
|
398
|
+
failed_count = 0
|
|
399
|
+
skipped_count = 0
|
|
400
|
+
|
|
401
|
+
for agent_name, (success, message) in results.items():
|
|
402
|
+
if success:
|
|
403
|
+
if args.dry_run:
|
|
404
|
+
print(f"⏳ Would install to {agent_name}...")
|
|
405
|
+
else:
|
|
406
|
+
agent_path = get_agent_path(agent_name)
|
|
407
|
+
print(f"⏳ Installing to {agent_name}... ✅ {agent_path / skill_name}")
|
|
408
|
+
installed_count += 1
|
|
409
|
+
else:
|
|
410
|
+
# Check if it's a permission error or skip
|
|
411
|
+
if "Permission denied" in message:
|
|
412
|
+
print(f"⏳ Installing to {agent_name}... ❌ Permission denied")
|
|
413
|
+
failed_count += 1
|
|
414
|
+
elif "does not exist" in message or "SKILL.md not found" in message:
|
|
415
|
+
# Validation error - only show once
|
|
416
|
+
print(message)
|
|
417
|
+
return 1
|
|
418
|
+
else:
|
|
419
|
+
print(f"⏳ Installing to {agent_name}... ⚠️ Skipped (not installed)")
|
|
420
|
+
skipped_count += 1
|
|
421
|
+
|
|
422
|
+
# Summary
|
|
423
|
+
print("\n📊 Summary:")
|
|
424
|
+
if args.dry_run:
|
|
425
|
+
print(f" Would install: {installed_count} agents")
|
|
426
|
+
else:
|
|
427
|
+
print(f" ✅ Installed: {installed_count} agents")
|
|
428
|
+
if failed_count > 0:
|
|
429
|
+
print(f" ❌ Failed: {failed_count} agent(s) (permission denied)")
|
|
430
|
+
if skipped_count > 0:
|
|
431
|
+
print(f" ⚠️ Skipped: {skipped_count} agent(s) (not installed)")
|
|
432
|
+
|
|
433
|
+
if not args.dry_run:
|
|
434
|
+
print("\nRestart your agents to load the skill.")
|
|
435
|
+
|
|
436
|
+
if failed_count > 0:
|
|
437
|
+
print("\nFix permission errors:")
|
|
438
|
+
print(" sudo mkdir -p ~/.amp && sudo chown -R $USER ~/.amp")
|
|
439
|
+
|
|
440
|
+
return 0 if installed_count > 0 else 1
|
|
441
|
+
|
|
442
|
+
# Single agent installation
|
|
443
|
+
agent_name = args.agent
|
|
444
|
+
|
|
445
|
+
print(f"\n📋 Installing skill: {skill_name}")
|
|
446
|
+
print(f" Agent: {agent_name}")
|
|
447
|
+
|
|
448
|
+
if args.dry_run:
|
|
449
|
+
print("\n🔍 DRY RUN MODE - No changes will be made\n")
|
|
450
|
+
|
|
451
|
+
success, message = install_to_agent(
|
|
452
|
+
skill_dir, agent_name, force=args.force, dry_run=args.dry_run
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
print(message)
|
|
456
|
+
|
|
457
|
+
return 0 if success else 1
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
if __name__ == "__main__":
|
|
461
|
+
sys.exit(main())
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Complete Skill Installation Workflow
|
|
4
|
+
One-command installation: fetch → scrape → enhance → package → upload
|
|
5
|
+
|
|
6
|
+
This CLI tool orchestrates the complete skill installation workflow by calling
|
|
7
|
+
the install_skill MCP tool.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
skill-seekers install --config react
|
|
11
|
+
skill-seekers install --config configs/custom.json --no-upload
|
|
12
|
+
skill-seekers install --config django --unlimited
|
|
13
|
+
skill-seekers install --config react --dry-run
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
# Install React skill from official configs
|
|
17
|
+
skill-seekers install --config react
|
|
18
|
+
|
|
19
|
+
# Install from local config file
|
|
20
|
+
skill-seekers install --config configs/custom.json
|
|
21
|
+
|
|
22
|
+
# Install without uploading
|
|
23
|
+
skill-seekers install --config django --no-upload
|
|
24
|
+
|
|
25
|
+
# Preview workflow without executing
|
|
26
|
+
skill-seekers install --config react --dry-run
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import asyncio
|
|
31
|
+
import sys
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
# Add parent directory to path to import MCP server
|
|
35
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
36
|
+
|
|
37
|
+
# Import the MCP tool function (with lazy loading)
|
|
38
|
+
try:
|
|
39
|
+
from skill_seekers.mcp.server import install_skill_tool
|
|
40
|
+
|
|
41
|
+
MCP_AVAILABLE = True
|
|
42
|
+
except ImportError:
|
|
43
|
+
MCP_AVAILABLE = False
|
|
44
|
+
install_skill_tool = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main():
|
|
48
|
+
"""Main entry point for CLI"""
|
|
49
|
+
# Check MCP availability first
|
|
50
|
+
if not MCP_AVAILABLE:
|
|
51
|
+
print("\n❌ Error: MCP package not installed")
|
|
52
|
+
print("\nThe 'install' command requires MCP support.")
|
|
53
|
+
print("Install with:")
|
|
54
|
+
print(" pip install skill-seekers[mcp]")
|
|
55
|
+
print("\nOr use these alternatives:")
|
|
56
|
+
print(" skill-seekers scrape --config react")
|
|
57
|
+
print(" skill-seekers package output/react/")
|
|
58
|
+
print()
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
|
|
61
|
+
parser = argparse.ArgumentParser(
|
|
62
|
+
description="Complete skill installation workflow (fetch → scrape → enhance → package → upload)",
|
|
63
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
64
|
+
epilog="""
|
|
65
|
+
Examples:
|
|
66
|
+
# Install React skill from official API
|
|
67
|
+
skill-seekers install --config react
|
|
68
|
+
|
|
69
|
+
# Install from local config file
|
|
70
|
+
skill-seekers install --config configs/custom.json
|
|
71
|
+
|
|
72
|
+
# Install without uploading
|
|
73
|
+
skill-seekers install --config django --no-upload
|
|
74
|
+
|
|
75
|
+
# Unlimited scraping (no page limits)
|
|
76
|
+
skill-seekers install --config godot --unlimited
|
|
77
|
+
|
|
78
|
+
# Preview workflow (dry run)
|
|
79
|
+
skill-seekers install --config react --dry-run
|
|
80
|
+
|
|
81
|
+
# Install for Gemini instead of Claude
|
|
82
|
+
skill-seekers install --config react --target gemini
|
|
83
|
+
|
|
84
|
+
# Install for OpenAI ChatGPT
|
|
85
|
+
skill-seekers install --config fastapi --target openai
|
|
86
|
+
|
|
87
|
+
Important:
|
|
88
|
+
- Enhancement is MANDATORY (30-60 sec) for quality (3/10→9/10)
|
|
89
|
+
- Total time: 20-45 minutes (mostly scraping)
|
|
90
|
+
- Multi-platform support: claude (default), gemini, openai, markdown
|
|
91
|
+
- Auto-uploads if API key is set (ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY)
|
|
92
|
+
|
|
93
|
+
Phases:
|
|
94
|
+
1. Fetch config (if config name provided)
|
|
95
|
+
2. Scrape documentation
|
|
96
|
+
3. AI Enhancement (MANDATORY - no skip option)
|
|
97
|
+
4. Package for target platform (ZIP or tar.gz)
|
|
98
|
+
5. Upload to target platform (optional)
|
|
99
|
+
""",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--config",
|
|
104
|
+
required=True,
|
|
105
|
+
help="Config name (e.g., 'react') or path (e.g., 'configs/custom.json')",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"--destination",
|
|
110
|
+
default="output",
|
|
111
|
+
help="Output directory for skill files (default: output/)",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
parser.add_argument("--no-upload", action="store_true", help="Skip automatic upload to Claude")
|
|
115
|
+
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"--unlimited",
|
|
118
|
+
action="store_true",
|
|
119
|
+
help="Remove page limits during scraping (WARNING: Can take hours)",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
parser.add_argument("--dry-run", action="store_true", help="Preview workflow without executing")
|
|
123
|
+
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--target",
|
|
126
|
+
choices=["claude", "gemini", "openai", "markdown"],
|
|
127
|
+
default="claude",
|
|
128
|
+
help="Target LLM platform (default: claude)",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
args = parser.parse_args()
|
|
132
|
+
|
|
133
|
+
# Determine if config is a name or path
|
|
134
|
+
config_arg = args.config
|
|
135
|
+
if config_arg.endswith(".json") or "/" in config_arg or "\\" in config_arg:
|
|
136
|
+
# It's a path
|
|
137
|
+
config_path = config_arg
|
|
138
|
+
config_name = None
|
|
139
|
+
else:
|
|
140
|
+
# It's a name
|
|
141
|
+
config_name = config_arg
|
|
142
|
+
config_path = None
|
|
143
|
+
|
|
144
|
+
# Build arguments for install_skill_tool
|
|
145
|
+
tool_args = {
|
|
146
|
+
"config_name": config_name,
|
|
147
|
+
"config_path": config_path,
|
|
148
|
+
"destination": args.destination,
|
|
149
|
+
"auto_upload": not args.no_upload,
|
|
150
|
+
"unlimited": args.unlimited,
|
|
151
|
+
"dry_run": args.dry_run,
|
|
152
|
+
"target": args.target,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Run async tool
|
|
156
|
+
try:
|
|
157
|
+
result = asyncio.run(install_skill_tool(tool_args))
|
|
158
|
+
|
|
159
|
+
# Print output
|
|
160
|
+
for content in result:
|
|
161
|
+
print(content.text)
|
|
162
|
+
|
|
163
|
+
# Return success/failure based on output
|
|
164
|
+
output_text = result[0].text
|
|
165
|
+
if "❌" in output_text and "WORKFLOW COMPLETE" not in output_text:
|
|
166
|
+
return 1
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
except KeyboardInterrupt:
|
|
170
|
+
print("\n\n⚠️ Workflow interrupted by user")
|
|
171
|
+
return 130 # Standard exit code for SIGINT
|
|
172
|
+
except Exception as e:
|
|
173
|
+
print(f"\n\n❌ Unexpected error: {str(e)}")
|
|
174
|
+
return 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
sys.exit(main())
|