agent-skill-manager 0.1.2__py3-none-any.whl → 0.2.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.
- agent_skill_manager-0.2.0.dist-info/METADATA +388 -0
- agent_skill_manager-0.2.0.dist-info/RECORD +12 -0
- skill_manager/__init__.py +17 -2
- skill_manager/agents.py +226 -38
- skill_manager/cli.py +562 -60
- skill_manager/deployment.py +118 -7
- skill_manager/github.py +155 -6
- skill_manager/metadata.py +7 -5
- skill_manager/removal.py +9 -10
- agent_skill_manager-0.1.2.dist-info/METADATA +0 -271
- agent_skill_manager-0.1.2.dist-info/RECORD +0 -12
- {agent_skill_manager-0.1.2.dist-info → agent_skill_manager-0.2.0.dist-info}/WHEEL +0 -0
- {agent_skill_manager-0.1.2.dist-info → agent_skill_manager-0.2.0.dist-info}/entry_points.txt +0 -0
skill_manager/cli.py
CHANGED
|
@@ -13,8 +13,10 @@ Commands:
|
|
|
13
13
|
update - Update skills from GitHub
|
|
14
14
|
update --all - Update all skills from GitHub
|
|
15
15
|
list - List installed skills and versions
|
|
16
|
+
discover - Discover skills in a GitHub repository
|
|
16
17
|
"""
|
|
17
18
|
|
|
19
|
+
import argparse
|
|
18
20
|
import shutil
|
|
19
21
|
import sys
|
|
20
22
|
from pathlib import Path
|
|
@@ -27,14 +29,22 @@ from rich.panel import Panel
|
|
|
27
29
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
28
30
|
from rich.table import Table
|
|
29
31
|
|
|
30
|
-
from .
|
|
32
|
+
from . import __version__
|
|
33
|
+
from .agents import AGENTS, detect_existing_agents, get_agent_name, get_agent_path, supports_global_deployment
|
|
31
34
|
from .deployment import (
|
|
32
35
|
deploy_multiple_skills,
|
|
33
36
|
deploy_skill_to_agents,
|
|
37
|
+
is_symlink_supported,
|
|
34
38
|
update_all_skills,
|
|
35
39
|
update_skill,
|
|
36
40
|
)
|
|
37
|
-
from .github import
|
|
41
|
+
from .github import (
|
|
42
|
+
discover_skills_in_repo,
|
|
43
|
+
download_multiple_skills,
|
|
44
|
+
download_skill_from_github,
|
|
45
|
+
get_system_temp_dir,
|
|
46
|
+
parse_github_url,
|
|
47
|
+
)
|
|
38
48
|
from .metadata import (
|
|
39
49
|
list_updatable_skills,
|
|
40
50
|
read_skill_metadata,
|
|
@@ -57,6 +67,85 @@ from .validation import (
|
|
|
57
67
|
console = Console()
|
|
58
68
|
|
|
59
69
|
|
|
70
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
71
|
+
"""Create the argument parser with all subcommands."""
|
|
72
|
+
parser = argparse.ArgumentParser(
|
|
73
|
+
prog="sm",
|
|
74
|
+
description="CLI tool for managing AI agent skills",
|
|
75
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
|
|
78
|
+
|
|
79
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
80
|
+
|
|
81
|
+
# Install command
|
|
82
|
+
install_parser = subparsers.add_parser("install", help="Download and deploy skills from GitHub")
|
|
83
|
+
install_parser.add_argument("url", nargs="?", help="GitHub URL of the skill or repository")
|
|
84
|
+
install_parser.add_argument(
|
|
85
|
+
"-a", "--agent", action="append", dest="agents", help="Target agent(s) (can be specified multiple times)"
|
|
86
|
+
)
|
|
87
|
+
install_parser.add_argument(
|
|
88
|
+
"-t", "--type", choices=["global", "project"], default="global", help="Deployment type (default: global)"
|
|
89
|
+
)
|
|
90
|
+
install_parser.add_argument("-d", "--dest", type=Path, help="Destination directory for downloaded skills")
|
|
91
|
+
install_parser.add_argument("--symlink", action="store_true", help="Use symlinks instead of copying")
|
|
92
|
+
install_parser.add_argument("--discover", action="store_true", help="Discover and install all skills in a repository")
|
|
93
|
+
install_parser.add_argument("--no-deploy", action="store_true", help="Download only, do not deploy")
|
|
94
|
+
install_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts")
|
|
95
|
+
|
|
96
|
+
# Download command
|
|
97
|
+
download_parser = subparsers.add_parser("download", help="Download skills from GitHub")
|
|
98
|
+
download_parser.add_argument("url", nargs="?", help="GitHub URL of the skill or repository")
|
|
99
|
+
download_parser.add_argument("-d", "--dest", type=Path, help="Destination directory")
|
|
100
|
+
download_parser.add_argument("--discover", action="store_true", help="Discover all skills in a repository")
|
|
101
|
+
download_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts")
|
|
102
|
+
|
|
103
|
+
# Deploy command
|
|
104
|
+
deploy_parser = subparsers.add_parser("deploy", help="Deploy local skills to agents")
|
|
105
|
+
deploy_parser.add_argument("skills", nargs="*", help="Skill names or paths to deploy")
|
|
106
|
+
deploy_parser.add_argument("-a", "--agent", action="append", dest="agents", help="Target agent(s)")
|
|
107
|
+
deploy_parser.add_argument("-t", "--type", choices=["global", "project"], default="global", help="Deployment type")
|
|
108
|
+
deploy_parser.add_argument("--symlink", action="store_true", help="Use symlinks instead of copying")
|
|
109
|
+
deploy_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts")
|
|
110
|
+
|
|
111
|
+
# Discover command
|
|
112
|
+
discover_parser = subparsers.add_parser("discover", help="Discover skills in a GitHub repository")
|
|
113
|
+
discover_parser.add_argument("url", nargs="?", help="GitHub URL to scan")
|
|
114
|
+
|
|
115
|
+
# Uninstall command
|
|
116
|
+
uninstall_parser = subparsers.add_parser("uninstall", help="Remove skills from agents")
|
|
117
|
+
uninstall_parser.add_argument("skills", nargs="*", help="Skill names to uninstall")
|
|
118
|
+
uninstall_parser.add_argument("-a", "--agent", action="append", dest="agents", help="Target agent(s)")
|
|
119
|
+
uninstall_parser.add_argument("-t", "--type", choices=["global", "project"], default="global", help="Deployment type")
|
|
120
|
+
uninstall_parser.add_argument("--hard", action="store_true", help="Permanently delete (no trash)")
|
|
121
|
+
uninstall_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts")
|
|
122
|
+
|
|
123
|
+
# Restore command
|
|
124
|
+
restore_parser = subparsers.add_parser("restore", help="Restore deleted skills from trash")
|
|
125
|
+
restore_parser.add_argument("skills", nargs="*", help="Skill names to restore")
|
|
126
|
+
restore_parser.add_argument("-a", "--agent", action="append", dest="agents", help="Target agent(s)")
|
|
127
|
+
restore_parser.add_argument("-t", "--type", choices=["global", "project"], default="global", help="Deployment type")
|
|
128
|
+
restore_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts")
|
|
129
|
+
|
|
130
|
+
# Update command
|
|
131
|
+
update_parser = subparsers.add_parser("update", help="Update skills from GitHub")
|
|
132
|
+
update_parser.add_argument("skills", nargs="*", help="Skill names to update")
|
|
133
|
+
update_parser.add_argument("-a", "--agent", action="append", dest="agents", help="Target agent(s)")
|
|
134
|
+
update_parser.add_argument("-t", "--type", choices=["global", "project"], default="global", help="Deployment type")
|
|
135
|
+
update_parser.add_argument("--all", action="store_true", help="Update all skills")
|
|
136
|
+
update_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts")
|
|
137
|
+
|
|
138
|
+
# List command
|
|
139
|
+
list_parser = subparsers.add_parser("list", help="List installed skills")
|
|
140
|
+
list_parser.add_argument("-a", "--agent", action="append", dest="agents", help="Target agent(s)")
|
|
141
|
+
list_parser.add_argument("-t", "--type", choices=["global", "project"], default="global", help="Deployment type")
|
|
142
|
+
|
|
143
|
+
# Agents command (list available agents)
|
|
144
|
+
subparsers.add_parser("agents", help="List all supported agents")
|
|
145
|
+
|
|
146
|
+
return parser
|
|
147
|
+
|
|
148
|
+
|
|
60
149
|
def select_agents(existing_agents: dict[str, Path]) -> list[str]:
|
|
61
150
|
"""
|
|
62
151
|
Interactive prompt to select agents for deployment.
|
|
@@ -181,8 +270,7 @@ def cmd_download() -> int:
|
|
|
181
270
|
"""
|
|
182
271
|
console.print(
|
|
183
272
|
Panel.fit(
|
|
184
|
-
"[bold cyan]Download Skill[/bold cyan]\
|
|
185
|
-
"Download a skill from GitHub",
|
|
273
|
+
"[bold cyan]Download Skill[/bold cyan]\nDownload a skill from GitHub",
|
|
186
274
|
border_style="cyan",
|
|
187
275
|
)
|
|
188
276
|
)
|
|
@@ -255,9 +343,7 @@ def cmd_download() -> int:
|
|
|
255
343
|
|
|
256
344
|
# Validate
|
|
257
345
|
if not validate_skill(skill_dir):
|
|
258
|
-
console.print(
|
|
259
|
-
f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]"
|
|
260
|
-
)
|
|
346
|
+
console.print(f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]")
|
|
261
347
|
|
|
262
348
|
console.print(f"[green]✓[/green] Skill downloaded to: {skill_dir}\n")
|
|
263
349
|
return 0
|
|
@@ -276,8 +362,7 @@ def cmd_deploy() -> int:
|
|
|
276
362
|
"""
|
|
277
363
|
console.print(
|
|
278
364
|
Panel.fit(
|
|
279
|
-
"[bold cyan]Deploy Skills[/bold cyan]\
|
|
280
|
-
"Deploy local skills to AI agents",
|
|
365
|
+
"[bold cyan]Deploy Skills[/bold cyan]\nDeploy local skills to AI agents",
|
|
281
366
|
border_style="cyan",
|
|
282
367
|
)
|
|
283
368
|
)
|
|
@@ -308,9 +393,7 @@ def cmd_deploy() -> int:
|
|
|
308
393
|
# Detect agents
|
|
309
394
|
existing_agents = detect_existing_agents()
|
|
310
395
|
if existing_agents:
|
|
311
|
-
console.print(
|
|
312
|
-
f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n"
|
|
313
|
-
)
|
|
396
|
+
console.print(f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n")
|
|
314
397
|
|
|
315
398
|
# Select deployment type
|
|
316
399
|
deployment_type = select_deployment_type()
|
|
@@ -416,8 +499,7 @@ def cmd_install() -> int:
|
|
|
416
499
|
"""
|
|
417
500
|
console.print(
|
|
418
501
|
Panel.fit(
|
|
419
|
-
"[bold cyan]Install Skill[/bold cyan]\
|
|
420
|
-
"Download and deploy a skill from GitHub",
|
|
502
|
+
"[bold cyan]Install Skill[/bold cyan]\nDownload and deploy a skill from GitHub",
|
|
421
503
|
border_style="cyan",
|
|
422
504
|
)
|
|
423
505
|
)
|
|
@@ -490,9 +572,7 @@ def cmd_install() -> int:
|
|
|
490
572
|
|
|
491
573
|
# Validate
|
|
492
574
|
if not validate_skill(skill_dir):
|
|
493
|
-
console.print(
|
|
494
|
-
f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]"
|
|
495
|
-
)
|
|
575
|
+
console.print(f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]")
|
|
496
576
|
|
|
497
577
|
console.print(f"[green]✓[/green] Skill downloaded to: {skill_dir}\n")
|
|
498
578
|
|
|
@@ -515,9 +595,7 @@ def cmd_install() -> int:
|
|
|
515
595
|
# Detect agents
|
|
516
596
|
existing_agents = detect_existing_agents()
|
|
517
597
|
if existing_agents:
|
|
518
|
-
console.print(
|
|
519
|
-
f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n"
|
|
520
|
-
)
|
|
598
|
+
console.print(f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n")
|
|
521
599
|
|
|
522
600
|
# Select deployment type
|
|
523
601
|
deployment_type = select_deployment_type()
|
|
@@ -573,8 +651,7 @@ def cmd_uninstall() -> int:
|
|
|
573
651
|
"""
|
|
574
652
|
console.print(
|
|
575
653
|
Panel.fit(
|
|
576
|
-
"[bold cyan]Uninstall Skills[/bold cyan]\
|
|
577
|
-
"Remove skills from AI agents",
|
|
654
|
+
"[bold cyan]Uninstall Skills[/bold cyan]\nRemove skills from AI agents",
|
|
578
655
|
border_style="cyan",
|
|
579
656
|
)
|
|
580
657
|
)
|
|
@@ -724,8 +801,7 @@ def cmd_restore() -> int:
|
|
|
724
801
|
"""
|
|
725
802
|
console.print(
|
|
726
803
|
Panel.fit(
|
|
727
|
-
"[bold cyan]Restore Skills[/bold cyan]\
|
|
728
|
-
"Restore deleted skills from trash",
|
|
804
|
+
"[bold cyan]Restore Skills[/bold cyan]\nRestore deleted skills from trash",
|
|
729
805
|
border_style="cyan",
|
|
730
806
|
)
|
|
731
807
|
)
|
|
@@ -835,8 +911,7 @@ def cmd_restore() -> int:
|
|
|
835
911
|
if fail_count == 0:
|
|
836
912
|
console.print(
|
|
837
913
|
Panel.fit(
|
|
838
|
-
f"[bold green]✓ Restoration successful![/bold green]\n\
|
|
839
|
-
f"Restored {success_count} skills",
|
|
914
|
+
f"[bold green]✓ Restoration successful![/bold green]\n\nRestored {success_count} skills",
|
|
840
915
|
border_style="green",
|
|
841
916
|
)
|
|
842
917
|
)
|
|
@@ -915,7 +990,6 @@ def cmd_update() -> int:
|
|
|
915
990
|
for skill_info in all_updatable[agent_id]:
|
|
916
991
|
skill_name = skill_info["skill_name"]
|
|
917
992
|
metadata = skill_info["metadata"]
|
|
918
|
-
github_url = metadata.get("github_url", "")
|
|
919
993
|
updated_at = metadata.get("updated_at", "unknown")
|
|
920
994
|
choices.append(
|
|
921
995
|
Choice(
|
|
@@ -945,7 +1019,10 @@ def cmd_update() -> int:
|
|
|
945
1019
|
console.print()
|
|
946
1020
|
|
|
947
1021
|
# Show summary
|
|
948
|
-
total_count = sum(
|
|
1022
|
+
total_count = sum(
|
|
1023
|
+
len(skills) if isinstance(skills, list) else len(all_updatable[agent_id])
|
|
1024
|
+
for agent_id, skills in skills_to_update.items()
|
|
1025
|
+
)
|
|
949
1026
|
console.print(f"[yellow]Will update {total_count} skills:[/yellow]")
|
|
950
1027
|
for agent_id in sorted(skills_to_update.keys()):
|
|
951
1028
|
agent_name = get_agent_name(agent_id)
|
|
@@ -1005,8 +1082,7 @@ def cmd_update() -> int:
|
|
|
1005
1082
|
if total_failed == 0:
|
|
1006
1083
|
console.print(
|
|
1007
1084
|
Panel.fit(
|
|
1008
|
-
f"[bold green]✓ Update successful![/bold green]\n\
|
|
1009
|
-
f"Updated {total_success} skills",
|
|
1085
|
+
f"[bold green]✓ Update successful![/bold green]\n\nUpdated {total_success} skills",
|
|
1010
1086
|
border_style="green",
|
|
1011
1087
|
)
|
|
1012
1088
|
)
|
|
@@ -1031,8 +1107,7 @@ def cmd_list() -> int:
|
|
|
1031
1107
|
"""
|
|
1032
1108
|
console.print(
|
|
1033
1109
|
Panel.fit(
|
|
1034
|
-
"[bold cyan]List Installed Skills[/bold cyan]\
|
|
1035
|
-
"Show all installed skills with version information",
|
|
1110
|
+
"[bold cyan]List Installed Skills[/bold cyan]\nShow all installed skills with version information",
|
|
1036
1111
|
border_style="cyan",
|
|
1037
1112
|
)
|
|
1038
1113
|
)
|
|
@@ -1075,18 +1150,21 @@ def cmd_list() -> int:
|
|
|
1075
1150
|
if skill_md.exists():
|
|
1076
1151
|
mtime = skill_md.stat().st_mtime
|
|
1077
1152
|
from datetime import datetime
|
|
1153
|
+
|
|
1078
1154
|
version_info = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
|
|
1079
1155
|
else:
|
|
1080
1156
|
version_info = "unknown"
|
|
1081
1157
|
source = "Local"
|
|
1082
1158
|
github_url = ""
|
|
1083
1159
|
|
|
1084
|
-
all_skills_data[agent_id].append(
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1160
|
+
all_skills_data[agent_id].append(
|
|
1161
|
+
{
|
|
1162
|
+
"name": skill_name,
|
|
1163
|
+
"version": version_info,
|
|
1164
|
+
"source": source,
|
|
1165
|
+
"url": github_url,
|
|
1166
|
+
}
|
|
1167
|
+
)
|
|
1090
1168
|
|
|
1091
1169
|
if not all_skills_data:
|
|
1092
1170
|
console.print("[yellow]No skills found in selected agents.[/yellow]")
|
|
@@ -1121,6 +1199,416 @@ def cmd_list() -> int:
|
|
|
1121
1199
|
return 0
|
|
1122
1200
|
|
|
1123
1201
|
|
|
1202
|
+
def cmd_discover(args: argparse.Namespace) -> int:
|
|
1203
|
+
"""
|
|
1204
|
+
Discover command - Discover skills in a GitHub repository.
|
|
1205
|
+
|
|
1206
|
+
Args:
|
|
1207
|
+
args: Parsed command line arguments
|
|
1208
|
+
|
|
1209
|
+
Returns:
|
|
1210
|
+
Exit code (0 for success, non-zero for failure)
|
|
1211
|
+
"""
|
|
1212
|
+
console.print(
|
|
1213
|
+
Panel.fit(
|
|
1214
|
+
"[bold cyan]Discover Skills[/bold cyan]\nScan a GitHub repository for skills",
|
|
1215
|
+
border_style="cyan",
|
|
1216
|
+
)
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
# Get URL
|
|
1220
|
+
url = args.url
|
|
1221
|
+
if not url:
|
|
1222
|
+
url = inquirer.text(
|
|
1223
|
+
message="Enter GitHub URL to scan:",
|
|
1224
|
+
validate=lambda x: len(x) > 0,
|
|
1225
|
+
invalid_message="URL cannot be empty",
|
|
1226
|
+
).execute()
|
|
1227
|
+
|
|
1228
|
+
console.print()
|
|
1229
|
+
|
|
1230
|
+
# Parse URL to show info
|
|
1231
|
+
try:
|
|
1232
|
+
owner, repo, branch, path = parse_github_url(url)
|
|
1233
|
+
console.print(f"[dim]Repository: {owner}/{repo}[/dim]")
|
|
1234
|
+
console.print(f"[dim]Branch: {branch}[/dim]")
|
|
1235
|
+
console.print(f"[dim]Path: {path or '(root)'}[/dim]\n")
|
|
1236
|
+
except ValueError as e:
|
|
1237
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1238
|
+
return 1
|
|
1239
|
+
|
|
1240
|
+
# Discover skills
|
|
1241
|
+
try:
|
|
1242
|
+
with Progress(
|
|
1243
|
+
SpinnerColumn(),
|
|
1244
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1245
|
+
console=console,
|
|
1246
|
+
) as progress:
|
|
1247
|
+
task = progress.add_task("Scanning repository for skills...", total=None)
|
|
1248
|
+
skills = discover_skills_in_repo(url)
|
|
1249
|
+
progress.update(task, completed=True)
|
|
1250
|
+
|
|
1251
|
+
if not skills:
|
|
1252
|
+
console.print("[yellow]No skills found in the repository.[/yellow]")
|
|
1253
|
+
return 0
|
|
1254
|
+
|
|
1255
|
+
# Display found skills
|
|
1256
|
+
table = Table(title=f"Found {len(skills)} Skills", show_header=True, header_style="bold cyan")
|
|
1257
|
+
table.add_column("Name", style="green", no_wrap=True)
|
|
1258
|
+
table.add_column("Path", style="yellow")
|
|
1259
|
+
table.add_column("URL", style="dim", overflow="fold")
|
|
1260
|
+
|
|
1261
|
+
for skill in sorted(skills, key=lambda x: x["name"]):
|
|
1262
|
+
table.add_row(
|
|
1263
|
+
skill["name"],
|
|
1264
|
+
skill["path"],
|
|
1265
|
+
skill["url"][:60] + "..." if len(skill["url"]) > 60 else skill["url"],
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
console.print(table)
|
|
1269
|
+
console.print()
|
|
1270
|
+
|
|
1271
|
+
console.print("[dim]Use 'sm install <url> --discover' to install all found skills[/dim]")
|
|
1272
|
+
return 0
|
|
1273
|
+
|
|
1274
|
+
except Exception as e:
|
|
1275
|
+
console.print(f"[red]Discovery failed: {e}[/red]")
|
|
1276
|
+
return 1
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def cmd_agents() -> int:
|
|
1280
|
+
"""
|
|
1281
|
+
Agents command - List all supported agents.
|
|
1282
|
+
|
|
1283
|
+
Returns:
|
|
1284
|
+
Exit code (0 for success)
|
|
1285
|
+
"""
|
|
1286
|
+
console.print(
|
|
1287
|
+
Panel.fit(
|
|
1288
|
+
"[bold cyan]Supported Agents[/bold cyan]\nAll AI agents supported by skill-manager",
|
|
1289
|
+
border_style="cyan",
|
|
1290
|
+
)
|
|
1291
|
+
)
|
|
1292
|
+
|
|
1293
|
+
table = Table(show_header=True, header_style="bold cyan")
|
|
1294
|
+
table.add_column("Agent ID", style="green", no_wrap=True)
|
|
1295
|
+
table.add_column("Name", style="yellow")
|
|
1296
|
+
table.add_column("Project Path", style="dim")
|
|
1297
|
+
table.add_column("Global Path", style="dim")
|
|
1298
|
+
|
|
1299
|
+
for agent_id in sorted(AGENTS.keys()):
|
|
1300
|
+
info = AGENTS[agent_id]
|
|
1301
|
+
global_path = info.get("global") or "N/A (project-only)"
|
|
1302
|
+
table.add_row(
|
|
1303
|
+
agent_id,
|
|
1304
|
+
info["name"],
|
|
1305
|
+
info["project"],
|
|
1306
|
+
global_path,
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1309
|
+
console.print(table)
|
|
1310
|
+
console.print(f"\n[dim]Total: {len(AGENTS)} agents supported[/dim]")
|
|
1311
|
+
return 0
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
def cmd_install_cli(args: argparse.Namespace) -> int:
|
|
1315
|
+
"""
|
|
1316
|
+
Install command with CLI arguments - Download and deploy skills.
|
|
1317
|
+
|
|
1318
|
+
Args:
|
|
1319
|
+
args: Parsed command line arguments
|
|
1320
|
+
|
|
1321
|
+
Returns:
|
|
1322
|
+
Exit code (0 for success, non-zero for failure)
|
|
1323
|
+
"""
|
|
1324
|
+
# If no URL provided, fall back to interactive mode
|
|
1325
|
+
if not args.url:
|
|
1326
|
+
return cmd_install()
|
|
1327
|
+
|
|
1328
|
+
console.print(
|
|
1329
|
+
Panel.fit(
|
|
1330
|
+
"[bold cyan]Install Skill[/bold cyan]\nDownload and deploy a skill from GitHub",
|
|
1331
|
+
border_style="cyan",
|
|
1332
|
+
)
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
url = args.url
|
|
1336
|
+
deployment_type = args.type
|
|
1337
|
+
use_symlink = args.symlink
|
|
1338
|
+
skip_deploy = args.no_deploy
|
|
1339
|
+
# auto_confirm = args.yes # Reserved for future use
|
|
1340
|
+
|
|
1341
|
+
# Parse URL to show info
|
|
1342
|
+
try:
|
|
1343
|
+
owner, repo, branch, path = parse_github_url(url)
|
|
1344
|
+
console.print(f"[dim]Repository: {owner}/{repo}[/dim]")
|
|
1345
|
+
console.print(f"[dim]Branch: {branch}[/dim]")
|
|
1346
|
+
console.print(f"[dim]Path: {path or '(root)'}[/dim]\n")
|
|
1347
|
+
except ValueError as e:
|
|
1348
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1349
|
+
return 1
|
|
1350
|
+
|
|
1351
|
+
# Determine destination directory
|
|
1352
|
+
if args.dest:
|
|
1353
|
+
dest_dir = args.dest
|
|
1354
|
+
else:
|
|
1355
|
+
dest_dir = get_system_temp_dir()
|
|
1356
|
+
|
|
1357
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
1358
|
+
console.print(f"[dim]Download location: {dest_dir}[/dim]\n")
|
|
1359
|
+
|
|
1360
|
+
# Check symlink support
|
|
1361
|
+
if use_symlink and not is_symlink_supported():
|
|
1362
|
+
console.print("[yellow]Warning: Symlinks not supported on this system, will copy instead[/yellow]")
|
|
1363
|
+
use_symlink = False
|
|
1364
|
+
|
|
1365
|
+
# Discover skills if requested
|
|
1366
|
+
if args.discover:
|
|
1367
|
+
with Progress(
|
|
1368
|
+
SpinnerColumn(),
|
|
1369
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1370
|
+
console=console,
|
|
1371
|
+
) as progress:
|
|
1372
|
+
task = progress.add_task("Discovering skills...", total=None)
|
|
1373
|
+
skills_info = discover_skills_in_repo(url)
|
|
1374
|
+
progress.update(task, completed=True)
|
|
1375
|
+
|
|
1376
|
+
if not skills_info:
|
|
1377
|
+
console.print("[yellow]No skills found in the repository.[/yellow]")
|
|
1378
|
+
return 0
|
|
1379
|
+
|
|
1380
|
+
console.print(f"[green]✓[/green] Found {len(skills_info)} skills\n")
|
|
1381
|
+
|
|
1382
|
+
# Download all skills
|
|
1383
|
+
with Progress(
|
|
1384
|
+
SpinnerColumn(),
|
|
1385
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1386
|
+
console=console,
|
|
1387
|
+
) as progress:
|
|
1388
|
+
task = progress.add_task("Downloading skills...", total=None)
|
|
1389
|
+
downloaded = download_multiple_skills(skills_info, dest_dir)
|
|
1390
|
+
progress.update(task, completed=True)
|
|
1391
|
+
|
|
1392
|
+
console.print(f"[green]✓[/green] Downloaded {len(downloaded)} skills\n")
|
|
1393
|
+
|
|
1394
|
+
# Save metadata for each
|
|
1395
|
+
for skill_dir, metadata in downloaded:
|
|
1396
|
+
save_skill_metadata(
|
|
1397
|
+
skill_dir,
|
|
1398
|
+
metadata["url"],
|
|
1399
|
+
metadata["owner"],
|
|
1400
|
+
metadata["repo"],
|
|
1401
|
+
metadata["branch"],
|
|
1402
|
+
metadata["path"],
|
|
1403
|
+
)
|
|
1404
|
+
else:
|
|
1405
|
+
# Download single skill
|
|
1406
|
+
try:
|
|
1407
|
+
with Progress(
|
|
1408
|
+
SpinnerColumn(),
|
|
1409
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1410
|
+
console=console,
|
|
1411
|
+
) as progress:
|
|
1412
|
+
task = progress.add_task("Downloading skill...", total=None)
|
|
1413
|
+
skill_dir, metadata = download_skill_from_github(url, dest_dir)
|
|
1414
|
+
progress.update(task, completed=True)
|
|
1415
|
+
|
|
1416
|
+
save_skill_metadata(
|
|
1417
|
+
skill_dir,
|
|
1418
|
+
url,
|
|
1419
|
+
metadata["owner"],
|
|
1420
|
+
metadata["repo"],
|
|
1421
|
+
metadata["branch"],
|
|
1422
|
+
metadata["path"],
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
if not validate_skill(skill_dir):
|
|
1426
|
+
console.print(f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]")
|
|
1427
|
+
|
|
1428
|
+
console.print(f"[green]✓[/green] Skill downloaded to: {skill_dir}\n")
|
|
1429
|
+
downloaded = [(skill_dir, metadata)]
|
|
1430
|
+
|
|
1431
|
+
except Exception as e:
|
|
1432
|
+
console.print(f"[red]Download failed: {e}[/red]")
|
|
1433
|
+
return 1
|
|
1434
|
+
|
|
1435
|
+
if skip_deploy:
|
|
1436
|
+
console.print("[dim]Skipping deployment (--no-deploy)[/dim]")
|
|
1437
|
+
return 0
|
|
1438
|
+
|
|
1439
|
+
# Determine agents
|
|
1440
|
+
if args.agents:
|
|
1441
|
+
selected_agents = args.agents
|
|
1442
|
+
# Validate agents
|
|
1443
|
+
for agent_id in selected_agents:
|
|
1444
|
+
if agent_id not in AGENTS:
|
|
1445
|
+
console.print(f"[red]Unknown agent: {agent_id}[/red]")
|
|
1446
|
+
console.print(f"[dim]Available agents: {', '.join(sorted(AGENTS.keys()))}[/dim]")
|
|
1447
|
+
return 1
|
|
1448
|
+
if deployment_type == "global" and not supports_global_deployment(agent_id):
|
|
1449
|
+
console.print(f"[red]Agent '{agent_id}' does not support global deployment[/red]")
|
|
1450
|
+
return 1
|
|
1451
|
+
else:
|
|
1452
|
+
# Interactive selection
|
|
1453
|
+
existing_agents = detect_existing_agents()
|
|
1454
|
+
if existing_agents:
|
|
1455
|
+
console.print(f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n")
|
|
1456
|
+
selected_agents = select_agents(existing_agents)
|
|
1457
|
+
if not selected_agents:
|
|
1458
|
+
console.print("[yellow]No agents selected.[/yellow]")
|
|
1459
|
+
return 0
|
|
1460
|
+
|
|
1461
|
+
console.print()
|
|
1462
|
+
|
|
1463
|
+
# Deploy
|
|
1464
|
+
project_root = get_project_root() if deployment_type == "project" else None
|
|
1465
|
+
total_success = 0
|
|
1466
|
+
total_fail = 0
|
|
1467
|
+
|
|
1468
|
+
for skill_dir, _ in downloaded:
|
|
1469
|
+
success_count, fail_count = deploy_skill_to_agents(
|
|
1470
|
+
skill_dir, selected_agents, deployment_type, project_root, use_symlink
|
|
1471
|
+
)
|
|
1472
|
+
total_success += success_count
|
|
1473
|
+
total_fail += fail_count
|
|
1474
|
+
|
|
1475
|
+
# Show results
|
|
1476
|
+
console.print()
|
|
1477
|
+
if total_fail == 0:
|
|
1478
|
+
link_type = "symlinked" if use_symlink else "deployed"
|
|
1479
|
+
console.print(
|
|
1480
|
+
Panel.fit(
|
|
1481
|
+
f"[bold green]✓ Installation successful![/bold green]\n\nSkills {link_type} to {total_success} agent(s)",
|
|
1482
|
+
border_style="green",
|
|
1483
|
+
)
|
|
1484
|
+
)
|
|
1485
|
+
else:
|
|
1486
|
+
console.print(
|
|
1487
|
+
Panel.fit(
|
|
1488
|
+
f"[bold yellow]⚠ Installation completed with errors[/bold yellow]\n\n"
|
|
1489
|
+
f"Success: {total_success} | Failed: {total_fail}",
|
|
1490
|
+
border_style="yellow",
|
|
1491
|
+
)
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
return 0 if total_fail == 0 else 1
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
def cmd_download_cli(args: argparse.Namespace) -> int:
|
|
1498
|
+
"""
|
|
1499
|
+
Download command with CLI arguments.
|
|
1500
|
+
|
|
1501
|
+
Args:
|
|
1502
|
+
args: Parsed command line arguments
|
|
1503
|
+
|
|
1504
|
+
Returns:
|
|
1505
|
+
Exit code (0 for success, non-zero for failure)
|
|
1506
|
+
"""
|
|
1507
|
+
# If no URL provided, fall back to interactive mode
|
|
1508
|
+
if not args.url:
|
|
1509
|
+
return cmd_download()
|
|
1510
|
+
|
|
1511
|
+
console.print(
|
|
1512
|
+
Panel.fit(
|
|
1513
|
+
"[bold cyan]Download Skill[/bold cyan]\nDownload a skill from GitHub",
|
|
1514
|
+
border_style="cyan",
|
|
1515
|
+
)
|
|
1516
|
+
)
|
|
1517
|
+
|
|
1518
|
+
url = args.url
|
|
1519
|
+
|
|
1520
|
+
# Parse URL to show info
|
|
1521
|
+
try:
|
|
1522
|
+
owner, repo, branch, path = parse_github_url(url)
|
|
1523
|
+
console.print(f"[dim]Repository: {owner}/{repo}[/dim]")
|
|
1524
|
+
console.print(f"[dim]Branch: {branch}[/dim]")
|
|
1525
|
+
console.print(f"[dim]Path: {path or '(root)'}[/dim]\n")
|
|
1526
|
+
except ValueError as e:
|
|
1527
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1528
|
+
return 1
|
|
1529
|
+
|
|
1530
|
+
# Determine destination directory
|
|
1531
|
+
if args.dest:
|
|
1532
|
+
dest_dir = args.dest
|
|
1533
|
+
else:
|
|
1534
|
+
dest_dir = get_system_temp_dir()
|
|
1535
|
+
|
|
1536
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
1537
|
+
console.print(f"[dim]Download location: {dest_dir}[/dim]\n")
|
|
1538
|
+
|
|
1539
|
+
# Discover skills if requested
|
|
1540
|
+
if args.discover:
|
|
1541
|
+
with Progress(
|
|
1542
|
+
SpinnerColumn(),
|
|
1543
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1544
|
+
console=console,
|
|
1545
|
+
) as progress:
|
|
1546
|
+
task = progress.add_task("Discovering skills...", total=None)
|
|
1547
|
+
skills_info = discover_skills_in_repo(url)
|
|
1548
|
+
progress.update(task, completed=True)
|
|
1549
|
+
|
|
1550
|
+
if not skills_info:
|
|
1551
|
+
console.print("[yellow]No skills found in the repository.[/yellow]")
|
|
1552
|
+
return 0
|
|
1553
|
+
|
|
1554
|
+
console.print(f"[green]✓[/green] Found {len(skills_info)} skills\n")
|
|
1555
|
+
|
|
1556
|
+
# Download all skills
|
|
1557
|
+
with Progress(
|
|
1558
|
+
SpinnerColumn(),
|
|
1559
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1560
|
+
console=console,
|
|
1561
|
+
) as progress:
|
|
1562
|
+
task = progress.add_task("Downloading skills...", total=None)
|
|
1563
|
+
downloaded = download_multiple_skills(skills_info, dest_dir)
|
|
1564
|
+
progress.update(task, completed=True)
|
|
1565
|
+
|
|
1566
|
+
console.print(f"[green]✓[/green] Downloaded {len(downloaded)} skills to {dest_dir}\n")
|
|
1567
|
+
|
|
1568
|
+
# Save metadata for each
|
|
1569
|
+
for skill_dir, metadata in downloaded:
|
|
1570
|
+
save_skill_metadata(
|
|
1571
|
+
skill_dir,
|
|
1572
|
+
metadata["url"],
|
|
1573
|
+
metadata["owner"],
|
|
1574
|
+
metadata["repo"],
|
|
1575
|
+
metadata["branch"],
|
|
1576
|
+
metadata["path"],
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
return 0
|
|
1580
|
+
else:
|
|
1581
|
+
# Download single skill
|
|
1582
|
+
try:
|
|
1583
|
+
with Progress(
|
|
1584
|
+
SpinnerColumn(),
|
|
1585
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1586
|
+
console=console,
|
|
1587
|
+
) as progress:
|
|
1588
|
+
task = progress.add_task("Downloading skill...", total=None)
|
|
1589
|
+
skill_dir, metadata = download_skill_from_github(url, dest_dir)
|
|
1590
|
+
progress.update(task, completed=True)
|
|
1591
|
+
|
|
1592
|
+
save_skill_metadata(
|
|
1593
|
+
skill_dir,
|
|
1594
|
+
url,
|
|
1595
|
+
metadata["owner"],
|
|
1596
|
+
metadata["repo"],
|
|
1597
|
+
metadata["branch"],
|
|
1598
|
+
metadata["path"],
|
|
1599
|
+
)
|
|
1600
|
+
|
|
1601
|
+
if not validate_skill(skill_dir):
|
|
1602
|
+
console.print(f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]")
|
|
1603
|
+
|
|
1604
|
+
console.print(f"[green]✓[/green] Skill downloaded to: {skill_dir}\n")
|
|
1605
|
+
return 0
|
|
1606
|
+
|
|
1607
|
+
except Exception as e:
|
|
1608
|
+
console.print(f"[red]Download failed: {e}[/red]")
|
|
1609
|
+
return 1
|
|
1610
|
+
|
|
1611
|
+
|
|
1124
1612
|
def main() -> int:
|
|
1125
1613
|
"""
|
|
1126
1614
|
Main CLI entry point.
|
|
@@ -1128,45 +1616,59 @@ def main() -> int:
|
|
|
1128
1616
|
Returns:
|
|
1129
1617
|
Exit code
|
|
1130
1618
|
"""
|
|
1619
|
+
parser = create_parser()
|
|
1620
|
+
|
|
1621
|
+
# Handle no arguments
|
|
1131
1622
|
if len(sys.argv) < 2:
|
|
1132
1623
|
console.print(
|
|
1133
1624
|
Panel.fit(
|
|
1134
|
-
"[bold cyan]Skill Manager[/bold cyan]\n\n"
|
|
1625
|
+
f"[bold cyan]Skill Manager[/bold cyan] [dim]v{__version__}[/dim]\n\n"
|
|
1135
1626
|
"Usage:\n"
|
|
1136
|
-
" sm
|
|
1137
|
-
" sm
|
|
1138
|
-
" sm
|
|
1139
|
-
" sm
|
|
1140
|
-
" sm
|
|
1141
|
-
" sm
|
|
1142
|
-
" sm update --all - Update
|
|
1143
|
-
" sm list
|
|
1144
|
-
"
|
|
1627
|
+
" sm install [url] - Download and deploy skills\n"
|
|
1628
|
+
" sm download [url] - Download a skill from GitHub\n"
|
|
1629
|
+
" sm deploy - Deploy local skills to agents\n"
|
|
1630
|
+
" sm discover [url] - Discover skills in a repository\n"
|
|
1631
|
+
" sm uninstall - Remove skills from agents\n"
|
|
1632
|
+
" sm restore - Restore deleted skills from trash\n"
|
|
1633
|
+
" sm update [--all] - Update skills from GitHub\n"
|
|
1634
|
+
" sm list - List installed skills\n"
|
|
1635
|
+
" sm agents - List all supported agents\n"
|
|
1636
|
+
" sm --version - Show version information\n\n"
|
|
1637
|
+
"[bold]CLI Options:[/bold]\n"
|
|
1638
|
+
" -a, --agent AGENT - Target agent(s)\n"
|
|
1639
|
+
" -t, --type TYPE - Deployment type (global/project)\n"
|
|
1640
|
+
" --symlink - Use symlinks instead of copying\n"
|
|
1641
|
+
" --discover - Discover all skills in repository\n"
|
|
1642
|
+
" -y, --yes - Skip confirmation prompts\n\n"
|
|
1643
|
+
"[dim]Example: sm install https://github.com/cloudflare/skills --discover -a windsurf --symlink[/dim]",
|
|
1145
1644
|
border_style="cyan",
|
|
1146
1645
|
)
|
|
1147
1646
|
)
|
|
1148
1647
|
return 1
|
|
1149
1648
|
|
|
1150
|
-
|
|
1649
|
+
args = parser.parse_args()
|
|
1151
1650
|
|
|
1152
1651
|
try:
|
|
1153
|
-
if command == "download":
|
|
1154
|
-
return
|
|
1155
|
-
elif command == "deploy":
|
|
1652
|
+
if args.command == "download":
|
|
1653
|
+
return cmd_download_cli(args)
|
|
1654
|
+
elif args.command == "deploy":
|
|
1156
1655
|
return cmd_deploy()
|
|
1157
|
-
elif command == "install":
|
|
1158
|
-
return
|
|
1159
|
-
elif command == "
|
|
1656
|
+
elif args.command == "install":
|
|
1657
|
+
return cmd_install_cli(args)
|
|
1658
|
+
elif args.command == "discover":
|
|
1659
|
+
return cmd_discover(args)
|
|
1660
|
+
elif args.command == "uninstall":
|
|
1160
1661
|
return cmd_uninstall()
|
|
1161
|
-
elif command == "restore":
|
|
1662
|
+
elif args.command == "restore":
|
|
1162
1663
|
return cmd_restore()
|
|
1163
|
-
elif command == "update":
|
|
1664
|
+
elif args.command == "update":
|
|
1164
1665
|
return cmd_update()
|
|
1165
|
-
elif command == "list":
|
|
1666
|
+
elif args.command == "list":
|
|
1166
1667
|
return cmd_list()
|
|
1668
|
+
elif args.command == "agents":
|
|
1669
|
+
return cmd_agents()
|
|
1167
1670
|
else:
|
|
1168
|
-
|
|
1169
|
-
console.print("Available commands: download, deploy, install, uninstall, restore, update, list")
|
|
1671
|
+
parser.print_help()
|
|
1170
1672
|
return 1
|
|
1171
1673
|
except KeyboardInterrupt:
|
|
1172
1674
|
console.print("\n[yellow]Cancelled[/yellow]")
|