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/deployment.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Skill deployment functionality.
|
|
4
|
-
Handles copying skills to agent directories.
|
|
4
|
+
Handles copying skills to agent directories with optional symlink support.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import os
|
|
7
8
|
import shutil
|
|
8
9
|
from collections.abc import Callable
|
|
9
10
|
from pathlib import Path
|
|
@@ -17,6 +18,102 @@ from .metadata import (
|
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def is_symlink_supported() -> bool:
|
|
22
|
+
"""
|
|
23
|
+
Check if the current system supports symlinks.
|
|
24
|
+
|
|
25
|
+
On Windows, symlinks require admin privileges or developer mode.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True if symlinks are supported, False otherwise
|
|
29
|
+
"""
|
|
30
|
+
if os.name == "nt":
|
|
31
|
+
# Windows: test by trying to create a symlink
|
|
32
|
+
import tempfile
|
|
33
|
+
|
|
34
|
+
test_dir = Path(tempfile.gettempdir()) / ".symlink_test"
|
|
35
|
+
test_link = Path(tempfile.gettempdir()) / ".symlink_test_link"
|
|
36
|
+
try:
|
|
37
|
+
test_dir.mkdir(exist_ok=True)
|
|
38
|
+
if test_link.exists() or test_link.is_symlink():
|
|
39
|
+
test_link.unlink()
|
|
40
|
+
test_link.symlink_to(test_dir, target_is_directory=True)
|
|
41
|
+
test_link.unlink()
|
|
42
|
+
test_dir.rmdir()
|
|
43
|
+
return True
|
|
44
|
+
except OSError:
|
|
45
|
+
if test_dir.exists():
|
|
46
|
+
test_dir.rmdir()
|
|
47
|
+
return False
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def create_symlink(source: Path, target: Path) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Create a symlink from target to source.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
source: The source directory (the actual skill)
|
|
57
|
+
target: The target path where the symlink will be created
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if symlink was created successfully, False otherwise
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
# Ensure target parent directory exists
|
|
64
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
|
|
66
|
+
# Remove existing target if present
|
|
67
|
+
if target.exists() or target.is_symlink():
|
|
68
|
+
if target.is_symlink():
|
|
69
|
+
target.unlink()
|
|
70
|
+
elif target.is_dir():
|
|
71
|
+
shutil.rmtree(target)
|
|
72
|
+
else:
|
|
73
|
+
target.unlink()
|
|
74
|
+
|
|
75
|
+
# Create symlink
|
|
76
|
+
target.symlink_to(source, target_is_directory=True)
|
|
77
|
+
return True
|
|
78
|
+
except OSError:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def remove_symlink(target: Path) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Remove a symlink (or regular directory).
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
target: The symlink/directory to remove
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if removed successfully, False otherwise
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
if target.is_symlink():
|
|
94
|
+
target.unlink()
|
|
95
|
+
elif target.is_dir():
|
|
96
|
+
shutil.rmtree(target)
|
|
97
|
+
elif target.exists():
|
|
98
|
+
target.unlink()
|
|
99
|
+
return True
|
|
100
|
+
except OSError:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_skill_symlink(skill_path: Path) -> bool:
|
|
105
|
+
"""
|
|
106
|
+
Check if a skill is deployed as a symlink.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
skill_path: Path to the skill directory
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
True if the skill is a symlink, False otherwise
|
|
113
|
+
"""
|
|
114
|
+
return skill_path.is_symlink()
|
|
115
|
+
|
|
116
|
+
|
|
20
117
|
def deploy_skill(
|
|
21
118
|
skill_path: Path,
|
|
22
119
|
skills_dir: Path,
|
|
@@ -61,6 +158,7 @@ def deploy_skill_to_agents(
|
|
|
61
158
|
agents: list[str],
|
|
62
159
|
deployment_type: str = "global",
|
|
63
160
|
project_root: Path | None = None,
|
|
161
|
+
use_symlink: bool = False,
|
|
64
162
|
) -> tuple[int, int]:
|
|
65
163
|
"""
|
|
66
164
|
Deploy a skill directory to multiple agents.
|
|
@@ -72,6 +170,7 @@ def deploy_skill_to_agents(
|
|
|
72
170
|
agents: List of agent IDs to deploy to
|
|
73
171
|
deployment_type: Either "global" or "project"
|
|
74
172
|
project_root: Project root directory (required for project deployment)
|
|
173
|
+
use_symlink: If True, create symlinks instead of copying
|
|
75
174
|
|
|
76
175
|
Returns:
|
|
77
176
|
Tuple of (success_count, failure_count)
|
|
@@ -89,12 +188,24 @@ def deploy_skill_to_agents(
|
|
|
89
188
|
target_base.mkdir(parents=True, exist_ok=True)
|
|
90
189
|
|
|
91
190
|
# Remove existing skill if present
|
|
92
|
-
if target_dir.exists():
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
191
|
+
if target_dir.exists() or target_dir.is_symlink():
|
|
192
|
+
if target_dir.is_symlink():
|
|
193
|
+
target_dir.unlink()
|
|
194
|
+
else:
|
|
195
|
+
shutil.rmtree(target_dir)
|
|
196
|
+
|
|
197
|
+
if use_symlink:
|
|
198
|
+
# Create symlink
|
|
199
|
+
if create_symlink(skill_dir.resolve(), target_dir):
|
|
200
|
+
success_count += 1
|
|
201
|
+
else:
|
|
202
|
+
# Fallback to copy if symlink fails
|
|
203
|
+
shutil.copytree(skill_dir, target_dir)
|
|
204
|
+
success_count += 1
|
|
205
|
+
else:
|
|
206
|
+
# Copy the skill directory
|
|
207
|
+
shutil.copytree(skill_dir, target_dir)
|
|
208
|
+
success_count += 1
|
|
98
209
|
except Exception:
|
|
99
210
|
fail_count += 1
|
|
100
211
|
|
skill_manager/github.py
CHANGED
|
@@ -4,6 +4,7 @@ GitHub download functionality for skills.
|
|
|
4
4
|
Handles URL parsing and downloading files/directories from GitHub.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import tempfile
|
|
7
8
|
from collections.abc import Callable
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from urllib.parse import urlparse
|
|
@@ -11,6 +12,16 @@ from urllib.parse import urlparse
|
|
|
11
12
|
import httpx
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
def get_system_temp_dir() -> Path:
|
|
16
|
+
"""
|
|
17
|
+
Get the system temporary directory (cross-platform).
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Path to the system temp directory
|
|
21
|
+
"""
|
|
22
|
+
return Path(tempfile.gettempdir()) / "skill-manager"
|
|
23
|
+
|
|
24
|
+
|
|
14
25
|
def parse_github_url(url: str) -> tuple[str, str, str, str]:
|
|
15
26
|
"""
|
|
16
27
|
Parse GitHub URL to extract repository information.
|
|
@@ -92,9 +103,7 @@ def download_file(url: str, dest_path: Path) -> None:
|
|
|
92
103
|
dest_path.write_bytes(response.content)
|
|
93
104
|
|
|
94
105
|
|
|
95
|
-
def download_directory(
|
|
96
|
-
owner: str, repo: str, path: str, dest_dir: Path, branch: str = "main"
|
|
97
|
-
) -> None:
|
|
106
|
+
def download_directory(owner: str, repo: str, path: str, dest_dir: Path, branch: str = "main") -> None:
|
|
98
107
|
"""
|
|
99
108
|
Recursively download an entire directory from GitHub.
|
|
100
109
|
|
|
@@ -124,9 +133,7 @@ def download_directory(
|
|
|
124
133
|
download_directory(owner, repo, item_path, subdir_dest, branch)
|
|
125
134
|
|
|
126
135
|
|
|
127
|
-
def download_skill_from_github(
|
|
128
|
-
url: str, dest_dir: Path, progress_callback: Callable | None = None
|
|
129
|
-
) -> tuple[Path, dict]:
|
|
136
|
+
def download_skill_from_github(url: str, dest_dir: Path, progress_callback: Callable | None = None) -> tuple[Path, dict]:
|
|
130
137
|
"""
|
|
131
138
|
Download a skill from GitHub to a local directory.
|
|
132
139
|
|
|
@@ -175,3 +182,145 @@ def download_skill_from_github(
|
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
return skill_dest, metadata
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def discover_skills_in_repo(url: str, progress_callback: Callable | None = None) -> list[dict]:
|
|
188
|
+
"""
|
|
189
|
+
Discover all SKILL.md files in a GitHub repository or directory.
|
|
190
|
+
|
|
191
|
+
This function scans a GitHub URL to find all directories containing SKILL.md files,
|
|
192
|
+
which conform to the skill specification.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
url: GitHub URL (can be repo root or a subdirectory)
|
|
196
|
+
progress_callback: Optional callback for progress updates
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of dictionaries containing skill information:
|
|
200
|
+
- name: skill directory name
|
|
201
|
+
- path: path within the repository
|
|
202
|
+
- url: full GitHub URL to the skill directory
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
httpx.HTTPStatusError: If the API request fails
|
|
206
|
+
"""
|
|
207
|
+
owner, repo, branch, path = parse_github_url(url)
|
|
208
|
+
|
|
209
|
+
if progress_callback:
|
|
210
|
+
progress_callback(f"Scanning {owner}/{repo}...")
|
|
211
|
+
|
|
212
|
+
skills = []
|
|
213
|
+
_scan_for_skills(owner, repo, branch, path, skills, progress_callback)
|
|
214
|
+
return skills
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _scan_for_skills(
|
|
218
|
+
owner: str,
|
|
219
|
+
repo: str,
|
|
220
|
+
branch: str,
|
|
221
|
+
path: str,
|
|
222
|
+
skills: list[dict],
|
|
223
|
+
progress_callback: Callable | None = None,
|
|
224
|
+
) -> None:
|
|
225
|
+
"""
|
|
226
|
+
Recursively scan a directory for SKILL.md files.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
owner: Repository owner
|
|
230
|
+
repo: Repository name
|
|
231
|
+
branch: Branch name
|
|
232
|
+
path: Current path being scanned
|
|
233
|
+
skills: List to append found skills to
|
|
234
|
+
progress_callback: Optional callback for progress updates
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
content = get_github_content(owner, repo, path, branch)
|
|
238
|
+
except httpx.HTTPStatusError:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
if not isinstance(content, list):
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
has_skill_md = False
|
|
245
|
+
subdirs = []
|
|
246
|
+
|
|
247
|
+
for item in content:
|
|
248
|
+
item_name = item["name"]
|
|
249
|
+
item_path = item["path"]
|
|
250
|
+
item_type = item["type"]
|
|
251
|
+
|
|
252
|
+
if item_type == "file" and item_name == "SKILL.md":
|
|
253
|
+
has_skill_md = True
|
|
254
|
+
elif item_type == "dir":
|
|
255
|
+
subdirs.append(item_path)
|
|
256
|
+
|
|
257
|
+
if has_skill_md:
|
|
258
|
+
skill_name = path.split("/")[-1] if path else repo
|
|
259
|
+
skill_url = f"https://github.com/{owner}/{repo}/tree/{branch}/{path}" if path else f"https://github.com/{owner}/{repo}"
|
|
260
|
+
skills.append(
|
|
261
|
+
{
|
|
262
|
+
"name": skill_name,
|
|
263
|
+
"path": path,
|
|
264
|
+
"url": skill_url,
|
|
265
|
+
"owner": owner,
|
|
266
|
+
"repo": repo,
|
|
267
|
+
"branch": branch,
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
if progress_callback:
|
|
271
|
+
progress_callback(f"Found skill: {skill_name}")
|
|
272
|
+
|
|
273
|
+
# Recursively scan subdirectories
|
|
274
|
+
for subdir in subdirs:
|
|
275
|
+
_scan_for_skills(owner, repo, branch, subdir, skills, progress_callback)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def download_multiple_skills(
|
|
279
|
+
skill_infos: list[dict],
|
|
280
|
+
dest_dir: Path,
|
|
281
|
+
progress_callback: Callable | None = None,
|
|
282
|
+
) -> list[tuple[Path, dict]]:
|
|
283
|
+
"""
|
|
284
|
+
Download multiple skills from GitHub.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
skill_infos: List of skill info dictionaries (from discover_skills_in_repo)
|
|
288
|
+
dest_dir: Destination directory
|
|
289
|
+
progress_callback: Optional callback for progress updates
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of tuples (skill_path, metadata)
|
|
293
|
+
"""
|
|
294
|
+
results = []
|
|
295
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
296
|
+
|
|
297
|
+
for skill_info in skill_infos:
|
|
298
|
+
owner = skill_info["owner"]
|
|
299
|
+
repo = skill_info["repo"]
|
|
300
|
+
branch = skill_info["branch"]
|
|
301
|
+
path = skill_info["path"]
|
|
302
|
+
skill_name = skill_info["name"]
|
|
303
|
+
|
|
304
|
+
if progress_callback:
|
|
305
|
+
progress_callback(f"Downloading {skill_name}...")
|
|
306
|
+
|
|
307
|
+
skill_dest = dest_dir / skill_name
|
|
308
|
+
skill_dest.mkdir(parents=True, exist_ok=True)
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
download_directory(owner, repo, path, skill_dest, branch)
|
|
312
|
+
|
|
313
|
+
metadata = {
|
|
314
|
+
"owner": owner,
|
|
315
|
+
"repo": repo,
|
|
316
|
+
"branch": branch,
|
|
317
|
+
"path": path,
|
|
318
|
+
"url": skill_info["url"],
|
|
319
|
+
}
|
|
320
|
+
results.append((skill_dest, metadata))
|
|
321
|
+
except Exception:
|
|
322
|
+
# Skip failed downloads
|
|
323
|
+
if progress_callback:
|
|
324
|
+
progress_callback(f"Failed to download {skill_name}")
|
|
325
|
+
|
|
326
|
+
return results
|
skill_manager/metadata.py
CHANGED
|
@@ -117,11 +117,13 @@ def list_updatable_skills(
|
|
|
117
117
|
# Check for metadata
|
|
118
118
|
metadata = read_skill_metadata(skill_dir)
|
|
119
119
|
if metadata and metadata.get("source") == "github":
|
|
120
|
-
updatable_skills.append(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
updatable_skills.append(
|
|
121
|
+
{
|
|
122
|
+
"skill_name": skill_dir.name,
|
|
123
|
+
"skill_path": skill_dir,
|
|
124
|
+
"metadata": metadata,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
125
127
|
|
|
126
128
|
return sorted(updatable_skills, key=lambda x: x["skill_name"])
|
|
127
129
|
|
skill_manager/removal.py
CHANGED
|
@@ -65,10 +65,7 @@ def soft_delete_skill(
|
|
|
65
65
|
# Create metadata file
|
|
66
66
|
metadata_file = trash_dest / ".trash_metadata"
|
|
67
67
|
metadata_file.write_text(
|
|
68
|
-
f"deleted_at: {timestamp}\n"
|
|
69
|
-
f"original_path: {skill_dir}\n"
|
|
70
|
-
f"agent_id: {agent_id}\n"
|
|
71
|
-
f"deployment_type: {deployment_type}\n"
|
|
68
|
+
f"deleted_at: {timestamp}\noriginal_path: {skill_dir}\nagent_id: {agent_id}\ndeployment_type: {deployment_type}\n"
|
|
72
69
|
)
|
|
73
70
|
|
|
74
71
|
return True
|
|
@@ -149,12 +146,14 @@ def list_trashed_skills(
|
|
|
149
146
|
if line.startswith("deleted_at:"):
|
|
150
147
|
deleted_at = line.split(":", 1)[1].strip()
|
|
151
148
|
|
|
152
|
-
trashed_skills.append(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
trashed_skills.append(
|
|
150
|
+
{
|
|
151
|
+
"skill_name": skill_dir.name,
|
|
152
|
+
"deleted_at": deleted_at,
|
|
153
|
+
"trash_path": skill_dir,
|
|
154
|
+
"timestamp_dir": timestamp_dir.name,
|
|
155
|
+
}
|
|
156
|
+
)
|
|
158
157
|
|
|
159
158
|
return trashed_skills
|
|
160
159
|
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: agent-skill-manager
|
|
3
|
-
Version: 0.1.2
|
|
4
|
-
Summary: CLI tool for managing AI agent skills across multiple platforms
|
|
5
|
-
Project-URL: Homepage, https://github.com/ackness/skill-manager
|
|
6
|
-
Project-URL: Repository, https://github.com/ackness/skill-manager
|
|
7
|
-
Project-URL: Issues, https://github.com/ackness/skill-manager/issues
|
|
8
|
-
Project-URL: Documentation, https://github.com/ackness/skill-manager#readme
|
|
9
|
-
Author-email: ackness <ackness8@gmail.com>
|
|
10
|
-
License: MIT
|
|
11
|
-
Keywords: agents,ai,claude,cli,cursor,skills,windsurf
|
|
12
|
-
Classifier: Development Status :: 4 - Beta
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
-
Classifier: Topic :: Utilities
|
|
19
|
-
Requires-Python: >=3.13
|
|
20
|
-
Requires-Dist: httpx>=0.28.1
|
|
21
|
-
Requires-Dist: inquirerpy>=0.3.4
|
|
22
|
-
Requires-Dist: loguru>=0.7.3
|
|
23
|
-
Requires-Dist: rich>=14.2.0
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
|
-
# Agent Skill Manager
|
|
27
|
-
|
|
28
|
-
A comprehensive CLI tool for managing AI agent skills across multiple platforms. Download, deploy, update, and manage skills for AI coding assistants like Claude Code, Cursor, Windsurf, and more.
|
|
29
|
-
|
|
30
|
-
[](https://pypi.org/project/agent-skill-manager/)
|
|
31
|
-
[](https://www.python.org/downloads/)
|
|
32
|
-
[](https://opensource.org/licenses/MIT)
|
|
33
|
-
|
|
34
|
-
## Features
|
|
35
|
-
|
|
36
|
-
- 📥 **Download** skills from GitHub with metadata tracking
|
|
37
|
-
- 🚀 **Deploy** skills to multiple AI agents (global or project-level)
|
|
38
|
-
- 🔄 **Update** skills automatically from GitHub sources
|
|
39
|
-
- 🗑️ **Uninstall** with safe deletion (move to trash) or hard delete
|
|
40
|
-
- ♻️ **Restore** deleted skills from trash
|
|
41
|
-
- 📋 **List** all installed skills with version information
|
|
42
|
-
|
|
43
|
-
## Supported AI Agents
|
|
44
|
-
|
|
45
|
-
- Claude Code
|
|
46
|
-
- Cursor
|
|
47
|
-
- Windsurf
|
|
48
|
-
- OpenCode
|
|
49
|
-
- GitHub Copilot
|
|
50
|
-
- Goose
|
|
51
|
-
- Gemini CLI
|
|
52
|
-
- Roo Code
|
|
53
|
-
- Kilo Code
|
|
54
|
-
- Amp
|
|
55
|
-
- Codex
|
|
56
|
-
- Antigravity
|
|
57
|
-
- Clawdbot
|
|
58
|
-
- Droid
|
|
59
|
-
|
|
60
|
-
## Installation
|
|
61
|
-
|
|
62
|
-
### Quick Run with uvx (No Installation Required)
|
|
63
|
-
|
|
64
|
-
Run directly without installing (recommended for trying it out):
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
uv tool install agent-skill-manager
|
|
68
|
-
sm
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Permanent Installation
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
# Using uv (recommended)
|
|
75
|
-
uv tool install agent-skill-manager
|
|
76
|
-
|
|
77
|
-
# Using pip
|
|
78
|
-
pip install agent-skill-manager
|
|
79
|
-
|
|
80
|
-
# Using pipx (isolated installation)
|
|
81
|
-
pipx install agent-skill-manager
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### From Source
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
git clone https://github.com/ackness/skill-manager.git
|
|
88
|
-
cd skill-manager
|
|
89
|
-
uv sync
|
|
90
|
-
uv pip install -e .
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Usage Methods Comparison
|
|
94
|
-
|
|
95
|
-
| Method | Command | Use Case |
|
|
96
|
-
|--------|---------|----------|
|
|
97
|
-
| **uvx (no install)** | `uvx --from agent-skill-manager sm install` | One-time use, testing, CI/CD |
|
|
98
|
-
| **uv tool install** | `uv tool install agent-skill-manager` then `sm install` | Regular use, isolated |
|
|
99
|
-
| **pip install** | `pip install agent-skill-manager` then `sm install` | Traditional installation |
|
|
100
|
-
| **From source** | `git clone ...` then `uv pip install -e .` | Development |
|
|
101
|
-
|
|
102
|
-
## Quick Start
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
# Run without installing (using uvx)
|
|
106
|
-
uvx --from agent-skill-manager sm install
|
|
107
|
-
uvx --from agent-skill-manager sm list
|
|
108
|
-
|
|
109
|
-
# Or after installation, use sm command directly:
|
|
110
|
-
sm install # Install a skill from GitHub
|
|
111
|
-
sm list # List installed skills
|
|
112
|
-
sm update --all # Update all skills
|
|
113
|
-
sm deploy # Deploy local skills to agents
|
|
114
|
-
sm uninstall # Uninstall a skill (safe delete)
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Commands
|
|
118
|
-
|
|
119
|
-
| Command | Description |
|
|
120
|
-
|---------|-------------|
|
|
121
|
-
| `sm download` | Download a skill from GitHub |
|
|
122
|
-
| `sm deploy` | Deploy local skills to agents |
|
|
123
|
-
| `sm install` | Download and deploy in one step |
|
|
124
|
-
| `sm uninstall` | Remove skills (safe delete/hard delete) |
|
|
125
|
-
| `sm restore` | Restore deleted skills from trash |
|
|
126
|
-
| `sm update` | Update selected skills from GitHub |
|
|
127
|
-
| `sm update --all` | Update all GitHub-sourced skills |
|
|
128
|
-
| `sm list` | Show installed skills with versions |
|
|
129
|
-
|
|
130
|
-
## Usage Examples
|
|
131
|
-
|
|
132
|
-
### Install a skill from GitHub
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
sm install
|
|
136
|
-
# Enter URL: https://github.com/user/repo/tree/main/skills/example-skill
|
|
137
|
-
# Follow the prompts to save locally and deploy
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Update all skills
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
sm update --all
|
|
144
|
-
# Automatically updates all skills installed from GitHub
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### List installed skills with versions
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
sm list
|
|
151
|
-
# Shows a table for each agent with:
|
|
152
|
-
# - Skill Name
|
|
153
|
-
# - Version/Updated timestamp
|
|
154
|
-
# - Source (GitHub/Local)
|
|
155
|
-
# - GitHub URL (for updatable skills)
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Safe delete and restore
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
# Uninstall with safe delete (default)
|
|
162
|
-
sm uninstall
|
|
163
|
-
|
|
164
|
-
# Restore if needed
|
|
165
|
-
sm restore
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Version Tracking
|
|
169
|
-
|
|
170
|
-
The tool uses two methods for version identification:
|
|
171
|
-
|
|
172
|
-
1. **GitHub Metadata** (for installed skills)
|
|
173
|
-
- Tracks installation and update timestamps
|
|
174
|
-
- Stores repository information
|
|
175
|
-
- Enables automatic updates
|
|
176
|
-
|
|
177
|
-
2. **File Modification Time** (for local skills)
|
|
178
|
-
- Uses SKILL.md modification time as fallback
|
|
179
|
-
- For skills without metadata
|
|
180
|
-
|
|
181
|
-
## Directory Structure
|
|
182
|
-
|
|
183
|
-
### Global Installation
|
|
184
|
-
Skills are available to all projects:
|
|
185
|
-
```
|
|
186
|
-
~/.claude/skills/ # Claude Code
|
|
187
|
-
~/.cursor/skills/ # Cursor
|
|
188
|
-
~/.codeium/windsurf/skills/ # Windsurf
|
|
189
|
-
# ... other agents
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Project Installation
|
|
193
|
-
Skills are only available in the current project:
|
|
194
|
-
```
|
|
195
|
-
project-root/
|
|
196
|
-
.claude/skills/
|
|
197
|
-
.cursor/skills/
|
|
198
|
-
# ... other agents
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
## Configuration
|
|
202
|
-
|
|
203
|
-
Each skill installed from GitHub includes metadata in `.skill_metadata.json`:
|
|
204
|
-
|
|
205
|
-
```json
|
|
206
|
-
{
|
|
207
|
-
"source": "github",
|
|
208
|
-
"github_url": "https://github.com/...",
|
|
209
|
-
"owner": "user",
|
|
210
|
-
"repo": "repo-name",
|
|
211
|
-
"branch": "main",
|
|
212
|
-
"path": "skills/skill-name",
|
|
213
|
-
"installed_at": "2026-01-20T14:30:52+00:00",
|
|
214
|
-
"updated_at": "2026-01-20T14:30:52+00:00"
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Development
|
|
219
|
-
|
|
220
|
-
### Adding Support for New Agents
|
|
221
|
-
|
|
222
|
-
Edit `src/skill_manager/agents.py` and add the agent configuration:
|
|
223
|
-
|
|
224
|
-
```python
|
|
225
|
-
"agent-id": {
|
|
226
|
-
"name": "Agent Name",
|
|
227
|
-
"project": ".agent/skills/",
|
|
228
|
-
"global": "~/.agent/skills/",
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### Running Tests
|
|
233
|
-
|
|
234
|
-
```bash
|
|
235
|
-
uv run pytest
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Code Formatting
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
uv run ruff format .
|
|
242
|
-
uv run ruff check . --fix
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Contributing
|
|
246
|
-
|
|
247
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
248
|
-
|
|
249
|
-
1. Fork the repository
|
|
250
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
251
|
-
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
252
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
253
|
-
5. Open a Pull Request
|
|
254
|
-
|
|
255
|
-
## Related Projects
|
|
256
|
-
|
|
257
|
-
- [Agent Skills Specification](https://agentskills.io/specification)
|
|
258
|
-
- [Agent Skills Registry](https://agentskills.io)
|
|
259
|
-
|
|
260
|
-
## License
|
|
261
|
-
|
|
262
|
-
MIT License - See [LICENSE](LICENSE) file for details
|
|
263
|
-
|
|
264
|
-
## Author
|
|
265
|
-
|
|
266
|
-
**ackness** - [ackness8@gmail.com](mailto:ackness8@gmail.com)
|
|
267
|
-
|
|
268
|
-
## Acknowledgments
|
|
269
|
-
|
|
270
|
-
- Built following the [Agent Skills specification](https://agentskills.io/specification)
|
|
271
|
-
- Supports all major AI coding assistants
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
skill_manager/__init__.py,sha256=0XIUCOGXT7Emm8ZLJ9YbNM-4Dv2v-m4Fkavq5onzwrY,1717
|
|
2
|
-
skill_manager/agents.py,sha256=1sqdXQwoZCXhedyfo608crZp3rjPX6-LpFU9WsK45sU,3590
|
|
3
|
-
skill_manager/cli.py,sha256=l5F_RDC7GFbsQZMRZhQ4GMPhiVAzyi8-hCB6sEy7OSc,36277
|
|
4
|
-
skill_manager/deployment.py,sha256=EVA-HdBcqcqz6IAZLoGU-BWEw0tOz_kjkRRxD_4ZC5Q,7672
|
|
5
|
-
skill_manager/github.py,sha256=WIju94QHVcJnCprwHujticgkeWs0Z07AXOJ6a39wKTg,4841
|
|
6
|
-
skill_manager/metadata.py,sha256=YF3vBxeujnbMwOJCM8G_zGBD6ZH5ZDMIBOpcKepBwAo,3457
|
|
7
|
-
skill_manager/removal.py,sha256=3yAn0Fd92_X40Z_SmxELHKKrWxew0vUGS8WO6Uq3730,8040
|
|
8
|
-
skill_manager/validation.py,sha256=fRUJDs4TiuKlH3doey31SPJfe671JJQcBFSAhJwoSIE,1970
|
|
9
|
-
agent_skill_manager-0.1.2.dist-info/METADATA,sha256=j3lF_fih1qOVaapFZql8WqgwCFg_EMv4rbsTUvmIDyM,6990
|
|
10
|
-
agent_skill_manager-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
-
agent_skill_manager-0.1.2.dist-info/entry_points.txt,sha256=ac3L07OC98p1llk259d9PUhDp-cl3ifV2nmYHGb3WO8,85
|
|
12
|
-
agent_skill_manager-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
{agent_skill_manager-0.1.2.dist-info → agent_skill_manager-0.2.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|