agent-skill-manager 0.1.3__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.1.3.dist-info → agent_skill_manager-0.2.0.dist-info}/METADATA +123 -28
- 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 +559 -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.0.dist-info}/WHEEL +0 -0
- {agent_skill_manager-0.1.3.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
|
|
@@ -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,85 @@ 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("--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
|
+
|
|
61
149
|
def select_agents(existing_agents: dict[str, Path]) -> list[str]:
|
|
62
150
|
"""
|
|
63
151
|
Interactive prompt to select agents for deployment.
|
|
@@ -182,8 +270,7 @@ def cmd_download() -> int:
|
|
|
182
270
|
"""
|
|
183
271
|
console.print(
|
|
184
272
|
Panel.fit(
|
|
185
|
-
"[bold cyan]Download Skill[/bold cyan]\
|
|
186
|
-
"Download a skill from GitHub",
|
|
273
|
+
"[bold cyan]Download Skill[/bold cyan]\nDownload a skill from GitHub",
|
|
187
274
|
border_style="cyan",
|
|
188
275
|
)
|
|
189
276
|
)
|
|
@@ -256,9 +343,7 @@ def cmd_download() -> int:
|
|
|
256
343
|
|
|
257
344
|
# Validate
|
|
258
345
|
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
|
-
)
|
|
346
|
+
console.print(f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]")
|
|
262
347
|
|
|
263
348
|
console.print(f"[green]✓[/green] Skill downloaded to: {skill_dir}\n")
|
|
264
349
|
return 0
|
|
@@ -277,8 +362,7 @@ def cmd_deploy() -> int:
|
|
|
277
362
|
"""
|
|
278
363
|
console.print(
|
|
279
364
|
Panel.fit(
|
|
280
|
-
"[bold cyan]Deploy Skills[/bold cyan]\
|
|
281
|
-
"Deploy local skills to AI agents",
|
|
365
|
+
"[bold cyan]Deploy Skills[/bold cyan]\nDeploy local skills to AI agents",
|
|
282
366
|
border_style="cyan",
|
|
283
367
|
)
|
|
284
368
|
)
|
|
@@ -309,9 +393,7 @@ def cmd_deploy() -> int:
|
|
|
309
393
|
# Detect agents
|
|
310
394
|
existing_agents = detect_existing_agents()
|
|
311
395
|
if existing_agents:
|
|
312
|
-
console.print(
|
|
313
|
-
f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n"
|
|
314
|
-
)
|
|
396
|
+
console.print(f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n")
|
|
315
397
|
|
|
316
398
|
# Select deployment type
|
|
317
399
|
deployment_type = select_deployment_type()
|
|
@@ -417,8 +499,7 @@ def cmd_install() -> int:
|
|
|
417
499
|
"""
|
|
418
500
|
console.print(
|
|
419
501
|
Panel.fit(
|
|
420
|
-
"[bold cyan]Install Skill[/bold cyan]\
|
|
421
|
-
"Download and deploy a skill from GitHub",
|
|
502
|
+
"[bold cyan]Install Skill[/bold cyan]\nDownload and deploy a skill from GitHub",
|
|
422
503
|
border_style="cyan",
|
|
423
504
|
)
|
|
424
505
|
)
|
|
@@ -491,9 +572,7 @@ def cmd_install() -> int:
|
|
|
491
572
|
|
|
492
573
|
# Validate
|
|
493
574
|
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
|
-
)
|
|
575
|
+
console.print(f"[yellow]Warning: {skill_dir} does not contain SKILL.md, may not be a valid skill[/yellow]")
|
|
497
576
|
|
|
498
577
|
console.print(f"[green]✓[/green] Skill downloaded to: {skill_dir}\n")
|
|
499
578
|
|
|
@@ -516,9 +595,7 @@ def cmd_install() -> int:
|
|
|
516
595
|
# Detect agents
|
|
517
596
|
existing_agents = detect_existing_agents()
|
|
518
597
|
if existing_agents:
|
|
519
|
-
console.print(
|
|
520
|
-
f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n"
|
|
521
|
-
)
|
|
598
|
+
console.print(f"[green]✓[/green] Detected {len(existing_agents)} installed agents\n")
|
|
522
599
|
|
|
523
600
|
# Select deployment type
|
|
524
601
|
deployment_type = select_deployment_type()
|
|
@@ -574,8 +651,7 @@ def cmd_uninstall() -> int:
|
|
|
574
651
|
"""
|
|
575
652
|
console.print(
|
|
576
653
|
Panel.fit(
|
|
577
|
-
"[bold cyan]Uninstall Skills[/bold cyan]\
|
|
578
|
-
"Remove skills from AI agents",
|
|
654
|
+
"[bold cyan]Uninstall Skills[/bold cyan]\nRemove skills from AI agents",
|
|
579
655
|
border_style="cyan",
|
|
580
656
|
)
|
|
581
657
|
)
|
|
@@ -725,8 +801,7 @@ def cmd_restore() -> int:
|
|
|
725
801
|
"""
|
|
726
802
|
console.print(
|
|
727
803
|
Panel.fit(
|
|
728
|
-
"[bold cyan]Restore Skills[/bold cyan]\
|
|
729
|
-
"Restore deleted skills from trash",
|
|
804
|
+
"[bold cyan]Restore Skills[/bold cyan]\nRestore deleted skills from trash",
|
|
730
805
|
border_style="cyan",
|
|
731
806
|
)
|
|
732
807
|
)
|
|
@@ -836,8 +911,7 @@ def cmd_restore() -> int:
|
|
|
836
911
|
if fail_count == 0:
|
|
837
912
|
console.print(
|
|
838
913
|
Panel.fit(
|
|
839
|
-
f"[bold green]✓ Restoration successful![/bold green]\n\
|
|
840
|
-
f"Restored {success_count} skills",
|
|
914
|
+
f"[bold green]✓ Restoration successful![/bold green]\n\nRestored {success_count} skills",
|
|
841
915
|
border_style="green",
|
|
842
916
|
)
|
|
843
917
|
)
|
|
@@ -916,7 +990,6 @@ def cmd_update() -> int:
|
|
|
916
990
|
for skill_info in all_updatable[agent_id]:
|
|
917
991
|
skill_name = skill_info["skill_name"]
|
|
918
992
|
metadata = skill_info["metadata"]
|
|
919
|
-
github_url = metadata.get("github_url", "")
|
|
920
993
|
updated_at = metadata.get("updated_at", "unknown")
|
|
921
994
|
choices.append(
|
|
922
995
|
Choice(
|
|
@@ -946,7 +1019,10 @@ def cmd_update() -> int:
|
|
|
946
1019
|
console.print()
|
|
947
1020
|
|
|
948
1021
|
# Show summary
|
|
949
|
-
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
|
+
)
|
|
950
1026
|
console.print(f"[yellow]Will update {total_count} skills:[/yellow]")
|
|
951
1027
|
for agent_id in sorted(skills_to_update.keys()):
|
|
952
1028
|
agent_name = get_agent_name(agent_id)
|
|
@@ -1006,8 +1082,7 @@ def cmd_update() -> int:
|
|
|
1006
1082
|
if total_failed == 0:
|
|
1007
1083
|
console.print(
|
|
1008
1084
|
Panel.fit(
|
|
1009
|
-
f"[bold green]✓ Update successful![/bold green]\n\
|
|
1010
|
-
f"Updated {total_success} skills",
|
|
1085
|
+
f"[bold green]✓ Update successful![/bold green]\n\nUpdated {total_success} skills",
|
|
1011
1086
|
border_style="green",
|
|
1012
1087
|
)
|
|
1013
1088
|
)
|
|
@@ -1032,8 +1107,7 @@ def cmd_list() -> int:
|
|
|
1032
1107
|
"""
|
|
1033
1108
|
console.print(
|
|
1034
1109
|
Panel.fit(
|
|
1035
|
-
"[bold cyan]List Installed Skills[/bold cyan]\
|
|
1036
|
-
"Show all installed skills with version information",
|
|
1110
|
+
"[bold cyan]List Installed Skills[/bold cyan]\nShow all installed skills with version information",
|
|
1037
1111
|
border_style="cyan",
|
|
1038
1112
|
)
|
|
1039
1113
|
)
|
|
@@ -1076,18 +1150,21 @@ def cmd_list() -> int:
|
|
|
1076
1150
|
if skill_md.exists():
|
|
1077
1151
|
mtime = skill_md.stat().st_mtime
|
|
1078
1152
|
from datetime import datetime
|
|
1153
|
+
|
|
1079
1154
|
version_info = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
|
|
1080
1155
|
else:
|
|
1081
1156
|
version_info = "unknown"
|
|
1082
1157
|
source = "Local"
|
|
1083
1158
|
github_url = ""
|
|
1084
1159
|
|
|
1085
|
-
all_skills_data[agent_id].append(
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
+
)
|
|
1091
1168
|
|
|
1092
1169
|
if not all_skills_data:
|
|
1093
1170
|
console.print("[yellow]No skills found in selected agents.[/yellow]")
|
|
@@ -1122,6 +1199,416 @@ def cmd_list() -> int:
|
|
|
1122
1199
|
return 0
|
|
1123
1200
|
|
|
1124
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
|
+
|
|
1125
1612
|
def main() -> int:
|
|
1126
1613
|
"""
|
|
1127
1614
|
Main CLI entry point.
|
|
@@ -1129,51 +1616,59 @@ def main() -> int:
|
|
|
1129
1616
|
Returns:
|
|
1130
1617
|
Exit code
|
|
1131
1618
|
"""
|
|
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
|
|
1619
|
+
parser = create_parser()
|
|
1136
1620
|
|
|
1621
|
+
# Handle no arguments
|
|
1137
1622
|
if len(sys.argv) < 2:
|
|
1138
1623
|
console.print(
|
|
1139
1624
|
Panel.fit(
|
|
1140
1625
|
f"[bold cyan]Skill Manager[/bold cyan] [dim]v{__version__}[/dim]\n\n"
|
|
1141
1626
|
"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
|
-
"
|
|
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]",
|
|
1152
1644
|
border_style="cyan",
|
|
1153
1645
|
)
|
|
1154
1646
|
)
|
|
1155
1647
|
return 1
|
|
1156
1648
|
|
|
1157
|
-
|
|
1649
|
+
args = parser.parse_args()
|
|
1158
1650
|
|
|
1159
1651
|
try:
|
|
1160
|
-
if command == "download":
|
|
1161
|
-
return
|
|
1162
|
-
elif command == "deploy":
|
|
1652
|
+
if args.command == "download":
|
|
1653
|
+
return cmd_download_cli(args)
|
|
1654
|
+
elif args.command == "deploy":
|
|
1163
1655
|
return cmd_deploy()
|
|
1164
|
-
elif command == "install":
|
|
1165
|
-
return
|
|
1166
|
-
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":
|
|
1167
1661
|
return cmd_uninstall()
|
|
1168
|
-
elif command == "restore":
|
|
1662
|
+
elif args.command == "restore":
|
|
1169
1663
|
return cmd_restore()
|
|
1170
|
-
elif command == "update":
|
|
1664
|
+
elif args.command == "update":
|
|
1171
1665
|
return cmd_update()
|
|
1172
|
-
elif command == "list":
|
|
1666
|
+
elif args.command == "list":
|
|
1173
1667
|
return cmd_list()
|
|
1668
|
+
elif args.command == "agents":
|
|
1669
|
+
return cmd_agents()
|
|
1174
1670
|
else:
|
|
1175
|
-
|
|
1176
|
-
console.print("Available commands: download, deploy, install, uninstall, restore, update, list, --version")
|
|
1671
|
+
parser.print_help()
|
|
1177
1672
|
return 1
|
|
1178
1673
|
except KeyboardInterrupt:
|
|
1179
1674
|
console.print("\n[yellow]Cancelled[/yellow]")
|