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.
@@ -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
@@ -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"