agentskills-adapters 0.4.0__tar.gz
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.
- agentskills_adapters-0.4.0/PKG-INFO +44 -0
- agentskills_adapters-0.4.0/README.md +22 -0
- agentskills_adapters-0.4.0/agentskills_adapters/__init__.py +10 -0
- agentskills_adapters-0.4.0/agentskills_adapters/importers.py +155 -0
- agentskills_adapters-0.4.0/agentskills_adapters/py.typed +0 -0
- agentskills_adapters-0.4.0/pyproject.toml +27 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentskills-adapters
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Adapters from common agent instruction formats to Agent Skills (https://agentskills.io)
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Pratik Panda
|
|
7
|
+
Requires-Python: >=3.12,<4.0
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Dist: agentskills-core (>=0.4.0,<1.0)
|
|
17
|
+
Requires-Dist: pyyaml (>=6.0,<7.0)
|
|
18
|
+
Project-URL: Homepage, https://agentskills.io
|
|
19
|
+
Project-URL: Repository, https://github.com/pratikxpanda/agentskills-sdk
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# agentskills-adapters
|
|
23
|
+
|
|
24
|
+
Import common agent instruction formats as ordinary `agentskills-core` `Skill` objects.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from agentskills_adapters import adapt_path, discover_sources
|
|
28
|
+
|
|
29
|
+
for source in discover_sources("."):
|
|
30
|
+
skill = adapt_path(source)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Supported sources:
|
|
34
|
+
|
|
35
|
+
- `AGENTS.md` and `.github/copilot-instructions.md`
|
|
36
|
+
- Cursor `.cursor/rules/*.mdc` files, preserving `globs` in `metadata`
|
|
37
|
+
- Claude-style skill folders containing `SKILL.md`
|
|
38
|
+
|
|
39
|
+
Formats without a description receive an explicit synthesized description such as
|
|
40
|
+
`Imported instructions from AGENTS.md.` They are never emitted with a blank catalog entry.
|
|
41
|
+
|
|
42
|
+
Use `agentskills init --from <path>` to write a native `SKILL.md` for editing and review.
|
|
43
|
+
Adapters are a migration path, not a fork of the Agent Skills specification.
|
|
44
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# agentskills-adapters
|
|
2
|
+
|
|
3
|
+
Import common agent instruction formats as ordinary `agentskills-core` `Skill` objects.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from agentskills_adapters import adapt_path, discover_sources
|
|
7
|
+
|
|
8
|
+
for source in discover_sources("."):
|
|
9
|
+
skill = adapt_path(source)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Supported sources:
|
|
13
|
+
|
|
14
|
+
- `AGENTS.md` and `.github/copilot-instructions.md`
|
|
15
|
+
- Cursor `.cursor/rules/*.mdc` files, preserving `globs` in `metadata`
|
|
16
|
+
- Claude-style skill folders containing `SKILL.md`
|
|
17
|
+
|
|
18
|
+
Formats without a description receive an explicit synthesized description such as
|
|
19
|
+
`Imported instructions from AGENTS.md.` They are never emitted with a blank catalog entry.
|
|
20
|
+
|
|
21
|
+
Use `agentskills init --from <path>` to write a native `SKILL.md` for editing and review.
|
|
22
|
+
Adapters are a migration path, not a fork of the Agent Skills specification.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Adapters from common agent instruction formats to Agent Skills."""
|
|
2
|
+
|
|
3
|
+
from agentskills_adapters.importers import (
|
|
4
|
+
ImportedSkill,
|
|
5
|
+
adapt_path,
|
|
6
|
+
discover_sources,
|
|
7
|
+
render_skill_md,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = ["ImportedSkill", "adapt_path", "discover_sources", "render_skill_md"]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Convert common agent instruction files into ``agentskills-core`` objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from agentskills_core import ResourceNotFoundError, Skill, SkillProvider
|
|
13
|
+
|
|
14
|
+
_HEADING = re.compile(r"^#{1,6}\s+(.+?)\s*#*\s*$")
|
|
15
|
+
_FRONTMATTER = re.compile(r"\A---\s*\n(.*?)\n---\s*\n?(.*)\Z", re.DOTALL)
|
|
16
|
+
_SUPPORTED_FILES = {"AGENTS.md", "copilot-instructions.md"}
|
|
17
|
+
_RESOURCE_KINDS = ("references", "scripts", "assets")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class ImportedSkill:
|
|
22
|
+
"""A native ``Skill`` plus the source and rendered ``SKILL.md`` text."""
|
|
23
|
+
|
|
24
|
+
skill: Skill
|
|
25
|
+
source: Path
|
|
26
|
+
skill_md: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _ImportedProvider(SkillProvider):
|
|
30
|
+
"""Serve one imported skill from immutable in-memory content."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, metadata: dict[str, Any], body: str) -> None:
|
|
33
|
+
self._metadata = metadata
|
|
34
|
+
self._body = body
|
|
35
|
+
|
|
36
|
+
async def get_metadata(self, skill_id: str) -> dict[str, Any]:
|
|
37
|
+
return self._metadata.copy()
|
|
38
|
+
|
|
39
|
+
async def get_body(self, skill_id: str) -> str:
|
|
40
|
+
return self._body
|
|
41
|
+
|
|
42
|
+
async def get_script(self, skill_id: str, name: str) -> bytes:
|
|
43
|
+
raise ResourceNotFoundError(f"Imported skill has no scripts: {name!r}")
|
|
44
|
+
|
|
45
|
+
async def get_asset(self, skill_id: str, name: str) -> bytes:
|
|
46
|
+
raise ResourceNotFoundError(f"Imported skill has no assets: {name!r}")
|
|
47
|
+
|
|
48
|
+
async def get_reference(self, skill_id: str, name: str) -> bytes:
|
|
49
|
+
raise ResourceNotFoundError(f"Imported skill has no references: {name!r}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _slug(value: str) -> str:
|
|
53
|
+
words = re.findall(r"[a-z0-9]+", value.lower())
|
|
54
|
+
return "-".join(words)[:64].strip("-") or "imported-skill"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _heading(text: str) -> str | None:
|
|
58
|
+
for line in text.splitlines():
|
|
59
|
+
match = _HEADING.match(line.strip())
|
|
60
|
+
if match:
|
|
61
|
+
return match.group(1).strip()
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _description(source: Path, body: str, explicit: str | None) -> str:
|
|
66
|
+
if explicit and explicit.strip():
|
|
67
|
+
return explicit.strip()
|
|
68
|
+
heading = _heading(body)
|
|
69
|
+
if heading:
|
|
70
|
+
return f"Imported instructions for {heading}."
|
|
71
|
+
return f"Imported instructions from {source.name}."
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _parse_source(path: Path) -> tuple[dict[str, Any], str]:
|
|
75
|
+
text = path.read_text(encoding="utf-8")
|
|
76
|
+
match = _FRONTMATTER.match(text)
|
|
77
|
+
if not match:
|
|
78
|
+
return {}, text
|
|
79
|
+
metadata = yaml.safe_load(match.group(1)) or {}
|
|
80
|
+
if not isinstance(metadata, dict):
|
|
81
|
+
raise ValueError(f"frontmatter in {path} must be a mapping")
|
|
82
|
+
return metadata, match.group(2)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _metadata(path: Path, raw: dict[str, Any], body: str, name: str | None) -> dict[str, Any]:
|
|
86
|
+
title = _heading(body) or path.stem
|
|
87
|
+
skill_name = _slug(name or raw.get("name") or title)
|
|
88
|
+
metadata: dict[str, Any] = {
|
|
89
|
+
"name": skill_name,
|
|
90
|
+
"description": _description(path, body, raw.get("description")),
|
|
91
|
+
}
|
|
92
|
+
if path.suffix.lower() == ".mdc" and raw.get("globs") is not None:
|
|
93
|
+
metadata["metadata"] = {"globs": raw["globs"], "source": "cursor"}
|
|
94
|
+
for key in ("license", "compatibility", "allowed-tools", "version"):
|
|
95
|
+
if key in raw:
|
|
96
|
+
metadata[key] = raw[key]
|
|
97
|
+
return metadata
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _render(metadata: dict[str, Any], body: str) -> str:
|
|
101
|
+
frontmatter = yaml.safe_dump(metadata, sort_keys=False, allow_unicode=False).strip()
|
|
102
|
+
return f"---\n{frontmatter}\n---\n\n{body.lstrip()}"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def adapt_path(
|
|
106
|
+
path: str | Path, *, name: str | None = None, description: str | None = None
|
|
107
|
+
) -> ImportedSkill:
|
|
108
|
+
"""Adapt one supported file or Claude-style skill directory.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
path: An ``AGENTS.md``, Copilot instructions file, Cursor ``.mdc``
|
|
112
|
+
rule, or directory containing ``SKILL.md``.
|
|
113
|
+
name: Optional native skill name override.
|
|
114
|
+
description: Optional explicit catalog description.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Imported skill handle, source path, and native ``SKILL.md`` text.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: If the source shape is unsupported or metadata is invalid.
|
|
121
|
+
"""
|
|
122
|
+
source = Path(path).expanduser().resolve()
|
|
123
|
+
if source.is_dir():
|
|
124
|
+
source = source / "SKILL.md"
|
|
125
|
+
if not source.is_file():
|
|
126
|
+
raise ValueError(f"unsupported adapter source: {path}")
|
|
127
|
+
if source.name not in _SUPPORTED_FILES and source.suffix.lower() not in {".mdc", ".md"}:
|
|
128
|
+
raise ValueError(f"unsupported adapter source: {path}")
|
|
129
|
+
|
|
130
|
+
raw, body = _parse_source(source)
|
|
131
|
+
metadata = _metadata(source, raw, body, name)
|
|
132
|
+
if description:
|
|
133
|
+
metadata["description"] = _description(source, body, description)
|
|
134
|
+
rendered = _render(metadata, body)
|
|
135
|
+
provider = _ImportedProvider(metadata, body)
|
|
136
|
+
return ImportedSkill(Skill(metadata["name"], provider), source, rendered)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def discover_sources(root: str | Path) -> list[Path]:
|
|
140
|
+
"""Find supported instruction sources below *root* without duplicates."""
|
|
141
|
+
base = Path(root).expanduser().resolve()
|
|
142
|
+
candidates: set[Path] = set()
|
|
143
|
+
for path in base.rglob("*"):
|
|
144
|
+
if not path.is_file():
|
|
145
|
+
continue
|
|
146
|
+
if path.name in _SUPPORTED_FILES or path.suffix.lower() == ".mdc":
|
|
147
|
+
candidates.add(path)
|
|
148
|
+
for path in base.rglob("SKILL.md"):
|
|
149
|
+
candidates.add(path)
|
|
150
|
+
return sorted(candidates)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def render_skill_md(imported: ImportedSkill) -> str:
|
|
154
|
+
"""Return the native ``SKILL.md`` text for an imported skill."""
|
|
155
|
+
return imported.skill_md
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "agentskills-adapters"
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "Adapters from common agent instruction formats to Agent Skills (https://agentskills.io)"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
authors = ["Pratik Panda"]
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://agentskills.io"
|
|
9
|
+
repository = "https://github.com/pratikxpanda/agentskills-sdk"
|
|
10
|
+
packages = [{include = "agentskills_adapters"}]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Topic :: Software Development :: Libraries",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[tool.poetry.dependencies]
|
|
21
|
+
python = ">=3.12,<4.0"
|
|
22
|
+
agentskills-core = ">=0.4.0,<1.0"
|
|
23
|
+
pyyaml = ">=6.0,<7.0"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["poetry-core"]
|
|
27
|
+
build-backend = "poetry.core.masonry.api"
|