agentskills-http 0.1.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,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentskills-http
3
+ Version: 0.1.0
4
+ Summary: HTTP-based skill providers for the Agent Skills format (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.1.0)
17
+ Requires-Dist: httpx (>=0.27)
18
+ Requires-Dist: pyyaml (>=6.0)
19
+ Project-URL: Homepage, https://agentskills.io
20
+ Project-URL: Repository, https://github.com/pratikxpanda/agentskills-sdk
21
+ Description-Content-Type: text/markdown
22
+
23
+ # agentskills-http
24
+
25
+ > HTTP static-file skill provider for the [Agent Skills SDK](../../README.md).
26
+
27
+ Serves [Agent Skills](https://agentskills.io) from any static HTTP file host — S3, Azure Blob, CDN, GitHub Pages, Nginx, etc. Expects the same directory-tree layout as the filesystem provider, served over HTTP.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install agentskills-http
33
+ ```
34
+
35
+ Requires Python 3.12+. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
36
+
37
+ ## Expected URL Layout
38
+
39
+ ```text
40
+ https://cdn.example.com/skills/
41
+ ├── incident-response/
42
+ │ ├── SKILL.md
43
+ │ ├── references/severity-levels.md
44
+ │ ├── scripts/page-oncall.sh
45
+ │ └── assets/flowchart.mermaid
46
+ └── another-skill/
47
+ └── SKILL.md
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ```python
53
+ from agentskills_core import SkillRegistry
54
+ from agentskills_http import HTTPStaticFileSkillProvider
55
+
56
+ async with HTTPStaticFileSkillProvider("https://cdn.example.com/skills") as provider:
57
+ registry = SkillRegistry()
58
+ await registry.register("incident-response", provider)
59
+
60
+ skill = registry.get_skill("incident-response")
61
+ meta = await skill.get_metadata()
62
+ body = await skill.get_body()
63
+ ```
64
+
65
+ ### Custom Headers
66
+
67
+ Pass authentication or other headers:
68
+
69
+ ```python
70
+ provider = HTTPStaticFileSkillProvider(
71
+ "https://cdn.example.com/skills",
72
+ headers={"Authorization": "Bearer <token>"},
73
+ )
74
+ ```
75
+
76
+ ### Bring Your Own Client
77
+
78
+ Supply a pre-configured `httpx.AsyncClient` for full control over timeouts, proxies, etc.:
79
+
80
+ ```python
81
+ import httpx
82
+
83
+ client = httpx.AsyncClient(timeout=30, headers={"Authorization": "Bearer <token>"})
84
+ provider = HTTPStaticFileSkillProvider("https://cdn.example.com/skills", client=client)
85
+ # caller is responsible for closing the client
86
+ ```
87
+
88
+ > **Note:** `client` and `headers` are mutually exclusive. Configure headers on the client directly when providing your own.
89
+
90
+ ## API
91
+
92
+ ### `HTTPStaticFileSkillProvider(base_url, *, client=None, headers=None)`
93
+
94
+ | Method | Returns | Description |
95
+ | --- | --- | --- |
96
+ | `get_metadata(skill_id)` | `dict[str, Any]` | Parsed YAML frontmatter from `SKILL.md` |
97
+ | `get_body(skill_id)` | `str` | Markdown body after the frontmatter |
98
+ | `get_script(skill_id, name)` | `bytes` | Raw script content |
99
+ | `get_asset(skill_id, name)` | `bytes` | Raw asset content |
100
+ | `get_reference(skill_id, name)` | `bytes` | Raw reference content |
101
+ | `aclose()` | `None` | Close the HTTP client (if owned by the provider) |
102
+
103
+ Supports `async with` for automatic cleanup.
104
+
105
+ ## Error Handling
106
+
107
+ | Scenario | Exception |
108
+ | --- | --- |
109
+ | 404 on `SKILL.md` | `SkillNotFoundError` |
110
+ | 404 on a resource | `ResourceNotFoundError` |
111
+ | Other HTTP errors (500, 403, ...) | `AgentSkillsError` |
112
+ | Connection failures | `AgentSkillsError` |
113
+
114
+ All exceptions inherit from `AgentSkillsError`.
115
+
116
+ ## License
117
+
118
+ MIT
119
+
@@ -0,0 +1,96 @@
1
+ # agentskills-http
2
+
3
+ > HTTP static-file skill provider for the [Agent Skills SDK](../../README.md).
4
+
5
+ Serves [Agent Skills](https://agentskills.io) from any static HTTP file host — S3, Azure Blob, CDN, GitHub Pages, Nginx, etc. Expects the same directory-tree layout as the filesystem provider, served over HTTP.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install agentskills-http
11
+ ```
12
+
13
+ Requires Python 3.12+. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
14
+
15
+ ## Expected URL Layout
16
+
17
+ ```text
18
+ https://cdn.example.com/skills/
19
+ ├── incident-response/
20
+ │ ├── SKILL.md
21
+ │ ├── references/severity-levels.md
22
+ │ ├── scripts/page-oncall.sh
23
+ │ └── assets/flowchart.mermaid
24
+ └── another-skill/
25
+ └── SKILL.md
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from agentskills_core import SkillRegistry
32
+ from agentskills_http import HTTPStaticFileSkillProvider
33
+
34
+ async with HTTPStaticFileSkillProvider("https://cdn.example.com/skills") as provider:
35
+ registry = SkillRegistry()
36
+ await registry.register("incident-response", provider)
37
+
38
+ skill = registry.get_skill("incident-response")
39
+ meta = await skill.get_metadata()
40
+ body = await skill.get_body()
41
+ ```
42
+
43
+ ### Custom Headers
44
+
45
+ Pass authentication or other headers:
46
+
47
+ ```python
48
+ provider = HTTPStaticFileSkillProvider(
49
+ "https://cdn.example.com/skills",
50
+ headers={"Authorization": "Bearer <token>"},
51
+ )
52
+ ```
53
+
54
+ ### Bring Your Own Client
55
+
56
+ Supply a pre-configured `httpx.AsyncClient` for full control over timeouts, proxies, etc.:
57
+
58
+ ```python
59
+ import httpx
60
+
61
+ client = httpx.AsyncClient(timeout=30, headers={"Authorization": "Bearer <token>"})
62
+ provider = HTTPStaticFileSkillProvider("https://cdn.example.com/skills", client=client)
63
+ # caller is responsible for closing the client
64
+ ```
65
+
66
+ > **Note:** `client` and `headers` are mutually exclusive. Configure headers on the client directly when providing your own.
67
+
68
+ ## API
69
+
70
+ ### `HTTPStaticFileSkillProvider(base_url, *, client=None, headers=None)`
71
+
72
+ | Method | Returns | Description |
73
+ | --- | --- | --- |
74
+ | `get_metadata(skill_id)` | `dict[str, Any]` | Parsed YAML frontmatter from `SKILL.md` |
75
+ | `get_body(skill_id)` | `str` | Markdown body after the frontmatter |
76
+ | `get_script(skill_id, name)` | `bytes` | Raw script content |
77
+ | `get_asset(skill_id, name)` | `bytes` | Raw asset content |
78
+ | `get_reference(skill_id, name)` | `bytes` | Raw reference content |
79
+ | `aclose()` | `None` | Close the HTTP client (if owned by the provider) |
80
+
81
+ Supports `async with` for automatic cleanup.
82
+
83
+ ## Error Handling
84
+
85
+ | Scenario | Exception |
86
+ | --- | --- |
87
+ | 404 on `SKILL.md` | `SkillNotFoundError` |
88
+ | 404 on a resource | `ResourceNotFoundError` |
89
+ | Other HTTP errors (500, 403, ...) | `AgentSkillsError` |
90
+ | Connection failures | `AgentSkillsError` |
91
+
92
+ All exceptions inherit from `AgentSkillsError`.
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,16 @@
1
+ """HTTP-based skill providers for the Agent Skills format.
2
+
3
+ This package provides :class:`HTTPStaticFileSkillProvider`, a concrete
4
+ implementation of :class:`~agentskills_core.SkillProvider` that fetches
5
+ `Agent Skills <https://agentskills.io>`_ from a static HTTP file host
6
+ (S3, Azure Blob Storage, CDN, GitHub Pages, or any web server serving
7
+ raw files).
8
+
9
+ Install::
10
+
11
+ pip install agentskills-http
12
+ """
13
+
14
+ from agentskills_http.static import HTTPStaticFileSkillProvider
15
+
16
+ __all__ = ["HTTPStaticFileSkillProvider"]
File without changes
@@ -0,0 +1,249 @@
1
+ """HTTP static-file skill provider.
2
+
3
+ This module implements :class:`HTTPStaticFileSkillProvider`, which fetches
4
+ `Agent Skills <https://agentskills.io>`_ from any static HTTP file host.
5
+ It expects the same directory-tree layout used by
6
+ :class:`~agentskills_fs.LocalFileSystemSkillProvider`, served over HTTP.
7
+
8
+ Expected URL layout::
9
+
10
+ {base_url}/
11
+ ├── incident-response/
12
+ │ ├── SKILL.md # YAML frontmatter + markdown body
13
+ │ ├── references/severity-levels.md
14
+ │ ├── scripts/page-oncall.sh
15
+ │ └── assets/flowchart.mermaid
16
+ └── another-skill/
17
+ └── SKILL.md
18
+
19
+ The provider is a pure content accessor — it does not enumerate or
20
+ discover skills. Registration is handled explicitly by the application
21
+ via :meth:`SkillRegistry.register <agentskills_core.SkillRegistry.register>`.
22
+ Resource names (scripts, assets, references) are discovered by the agent
23
+ from the skill body rather than from a manifest.
24
+
25
+ All methods are ``async`` and use `httpx <https://www.python-httpx.org/>`_
26
+ for non-blocking HTTP requests.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from typing import Any
32
+ from urllib.parse import quote
33
+
34
+ import httpx
35
+
36
+ from agentskills_core import (
37
+ AgentSkillsError,
38
+ ResourceNotFoundError,
39
+ SkillNotFoundError,
40
+ SkillProvider,
41
+ split_frontmatter,
42
+ )
43
+
44
+
45
+ class HTTPStaticFileSkillProvider(SkillProvider):
46
+ """Skill provider backed by a static HTTP file host.
47
+
48
+ The provider expects an HTTP server (S3, Azure Blob, CDN, Nginx,
49
+ GitHub Pages, etc.) that hosts skill files at predictable URL paths.
50
+ Resource names (scripts, assets, references) are discovered by the
51
+ agent from the skill body rather than from a separate manifest.
52
+
53
+ The provider owns an :class:`httpx.AsyncClient` for connection
54
+ pooling. If you supply your own client the provider will use it
55
+ without closing it. Otherwise call :meth:`aclose` or use
56
+ ``async with`` when you are finished.
57
+
58
+ Args:
59
+ base_url: Root URL where the skill tree is hosted. A trailing
60
+ slash is stripped automatically.
61
+ client: Optional pre-configured :class:`httpx.AsyncClient`.
62
+ When provided, the caller is responsible for closing it.
63
+ headers: Optional extra headers sent with every request (e.g.
64
+ ``Authorization``).
65
+
66
+ Example::
67
+
68
+ async with HTTPStaticFileSkillProvider("https://cdn.example.com/skills") as provider:
69
+ registry = SkillRegistry()
70
+ await registry.register("incident-response", provider)
71
+
72
+ skill = registry.get_skill("incident-response")
73
+ meta = await skill.get_metadata()
74
+ print(f"{meta['name']}: {meta['description']}")
75
+ """
76
+
77
+ def __init__(
78
+ self,
79
+ base_url: str,
80
+ *,
81
+ client: httpx.AsyncClient | None = None,
82
+ headers: dict[str, str] | None = None,
83
+ ) -> None:
84
+ if client is not None and headers is not None:
85
+ raise ValueError(
86
+ "Cannot specify both 'client' and 'headers'. "
87
+ "Configure headers on the client directly."
88
+ )
89
+ self._base_url = base_url.rstrip("/")
90
+ self._owns_client = client is None
91
+ self._client = client or httpx.AsyncClient(headers=headers)
92
+
93
+ async def aclose(self) -> None:
94
+ """Close the underlying HTTP client if it is owned by this provider."""
95
+ if self._owns_client:
96
+ await self._client.aclose()
97
+
98
+ async def __aenter__(self) -> HTTPStaticFileSkillProvider:
99
+ return self
100
+
101
+ async def __aexit__(self, *exc: object) -> None:
102
+ await self.aclose()
103
+
104
+ # ------------------------------------------------------------------
105
+ # Metadata & body
106
+ # ------------------------------------------------------------------
107
+
108
+ async def get_metadata(self, skill_id: str) -> dict[str, Any]:
109
+ """Fetch ``SKILL.md`` and return the parsed YAML frontmatter.
110
+
111
+ Args:
112
+ skill_id: Skill name to look up.
113
+
114
+ Returns:
115
+ Dictionary of frontmatter key-value pairs.
116
+
117
+ Raises:
118
+ SkillNotFoundError: If the skill's ``SKILL.md`` cannot be
119
+ fetched.
120
+ """
121
+ raw = await self._get_skill_md(skill_id)
122
+ frontmatter, _ = split_frontmatter(raw)
123
+ return frontmatter
124
+
125
+ async def get_body(self, skill_id: str) -> str:
126
+ """Fetch ``SKILL.md`` and return the markdown body.
127
+
128
+ Args:
129
+ skill_id: Skill name to look up.
130
+
131
+ Returns:
132
+ Markdown instruction text.
133
+
134
+ Raises:
135
+ SkillNotFoundError: If the skill's ``SKILL.md`` cannot be
136
+ fetched.
137
+ """
138
+ raw = await self._get_skill_md(skill_id)
139
+ _, body = split_frontmatter(raw)
140
+ return body
141
+
142
+ # ------------------------------------------------------------------
143
+ # Scripts
144
+ # ------------------------------------------------------------------
145
+
146
+ async def get_script(self, skill_id: str, name: str) -> bytes:
147
+ """Fetch a single script file.
148
+
149
+ Args:
150
+ skill_id: Skill name.
151
+ name: Script filename.
152
+
153
+ Returns:
154
+ Raw content as bytes.
155
+
156
+ Raises:
157
+ ResourceNotFoundError: If the script does not exist.
158
+ """
159
+ return await self._get_resource(skill_id, "scripts", name)
160
+
161
+ # ------------------------------------------------------------------
162
+ # Assets
163
+ # ------------------------------------------------------------------
164
+
165
+ async def get_asset(self, skill_id: str, name: str) -> bytes:
166
+ """Fetch a single asset file.
167
+
168
+ Args:
169
+ skill_id: Skill name.
170
+ name: Asset filename.
171
+
172
+ Returns:
173
+ Raw content as bytes.
174
+
175
+ Raises:
176
+ ResourceNotFoundError: If the asset does not exist.
177
+ """
178
+ return await self._get_resource(skill_id, "assets", name)
179
+
180
+ # ------------------------------------------------------------------
181
+ # References
182
+ # ------------------------------------------------------------------
183
+
184
+ async def get_reference(self, skill_id: str, name: str) -> bytes:
185
+ """Fetch a single reference file.
186
+
187
+ Args:
188
+ skill_id: Skill name.
189
+ name: Reference filename.
190
+
191
+ Returns:
192
+ Raw content as bytes.
193
+
194
+ Raises:
195
+ ResourceNotFoundError: If the reference does not exist.
196
+ """
197
+ return await self._get_resource(skill_id, "references", name)
198
+
199
+ # ------------------------------------------------------------------
200
+ # Internal helpers
201
+ # ------------------------------------------------------------------
202
+
203
+ async def _get_text(self, url: str) -> str:
204
+ """GET a URL and return the response text.
205
+
206
+ Raises:
207
+ SkillNotFoundError: On 404.
208
+ AgentSkillsError: On other HTTP or connection errors.
209
+ """
210
+ try:
211
+ resp = await self._client.get(url)
212
+ except httpx.HTTPError as exc:
213
+ raise AgentSkillsError(f"HTTP request failed: {url}") from exc
214
+ if resp.status_code == 404:
215
+ raise SkillNotFoundError(f"Not found: {url}")
216
+ try:
217
+ resp.raise_for_status()
218
+ except httpx.HTTPStatusError as exc:
219
+ raise AgentSkillsError(f"HTTP {resp.status_code} error for {url}") from exc
220
+ return resp.text
221
+
222
+ async def _get_bytes(self, url: str) -> bytes:
223
+ """GET a URL and return the response bytes.
224
+
225
+ Raises:
226
+ ResourceNotFoundError: On 404.
227
+ AgentSkillsError: On other HTTP or connection errors.
228
+ """
229
+ try:
230
+ resp = await self._client.get(url)
231
+ except httpx.HTTPError as exc:
232
+ raise AgentSkillsError(f"HTTP request failed: {url}") from exc
233
+ if resp.status_code == 404:
234
+ raise ResourceNotFoundError(f"Not found: {url}")
235
+ try:
236
+ resp.raise_for_status()
237
+ except httpx.HTTPStatusError as exc:
238
+ raise AgentSkillsError(f"HTTP {resp.status_code} error for {url}") from exc
239
+ return resp.content
240
+
241
+ async def _get_skill_md(self, skill_id: str) -> str:
242
+ """Fetch the full text of a skill's ``SKILL.md``."""
243
+ url = f"{self._base_url}/{quote(skill_id)}/SKILL.md"
244
+ return await self._get_text(url)
245
+
246
+ async def _get_resource(self, skill_id: str, subdir: str, name: str) -> bytes:
247
+ """Fetch a single resource file from a skill subdirectory."""
248
+ url = f"{self._base_url}/{quote(skill_id)}/{subdir}/{quote(name)}"
249
+ return await self._get_bytes(url)
@@ -0,0 +1,27 @@
1
+ [tool.poetry]
2
+ name = "agentskills-http"
3
+ version = "0.1.0"
4
+ description = "HTTP-based skill providers for the Agent Skills format (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_http"}]
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
+ ]
18
+
19
+ [tool.poetry.dependencies]
20
+ python = ">=3.12,<4.0"
21
+ agentskills-core = ">=0.1.0"
22
+ httpx = ">=0.27"
23
+ pyyaml = ">=6.0"
24
+
25
+ [build-system]
26
+ requires = ["poetry-core"]
27
+ build-backend = "poetry.core.masonry.api"