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