mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 01:21:43 +00:00
feat(skills): integrate skills.sh as a hub source
Add a skills.sh-backed source adapter for the Hermes Skills Hub. The new adapter uses skills.sh search results for discovery, falls back to featured homepage links for browse-style queries, and resolves installs / inspects through the underlying GitHub repo using common Agent Skills layout conventions. Also expose skills-sh in CLI source filters and add regression coverage for search, alias resolution, and source routing.
This commit is contained in:
parent
a0f0f4fe52
commit
483a0b5233
4 changed files with 370 additions and 4 deletions
|
|
@ -8,10 +8,12 @@ from tools.skills_hub import (
|
|||
GitHubAuth,
|
||||
GitHubSource,
|
||||
LobeHubSource,
|
||||
SkillsShSource,
|
||||
SkillMeta,
|
||||
SkillBundle,
|
||||
HubLockFile,
|
||||
TapsManager,
|
||||
create_source_router,
|
||||
unified_search,
|
||||
append_audit_log,
|
||||
_skill_meta_to_dict,
|
||||
|
|
@ -93,6 +95,129 @@ class TestTrustLevelFor:
|
|||
assert result in ("trusted", "community")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SkillsShSource
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestSkillsShSource:
|
||||
def _source(self):
|
||||
auth = MagicMock(spec=GitHubAuth)
|
||||
return SkillsShSource(auth=auth)
|
||||
|
||||
@patch("tools.skills_hub._write_index_cache")
|
||||
@patch("tools.skills_hub._read_index_cache", return_value=None)
|
||||
@patch("tools.skills_hub.httpx.get")
|
||||
def test_search_maps_skills_sh_results_to_prefixed_identifiers(self, mock_get, _mock_read_cache, _mock_write_cache):
|
||||
mock_get.return_value = MagicMock(
|
||||
status_code=200,
|
||||
json=lambda: {
|
||||
"skills": [
|
||||
{
|
||||
"id": "vercel-labs/agent-skills/vercel-react-best-practices",
|
||||
"skillId": "vercel-react-best-practices",
|
||||
"name": "vercel-react-best-practices",
|
||||
"installs": 207679,
|
||||
"source": "vercel-labs/agent-skills",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
results = self._source().search("react", limit=5)
|
||||
|
||||
assert len(results) == 1
|
||||
assert results[0].source == "skills.sh"
|
||||
assert results[0].identifier == "skills-sh/vercel-labs/agent-skills/vercel-react-best-practices"
|
||||
assert "skills.sh" in results[0].description
|
||||
assert results[0].repo == "vercel-labs/agent-skills"
|
||||
assert results[0].path == "vercel-react-best-practices"
|
||||
|
||||
@patch("tools.skills_hub._write_index_cache")
|
||||
@patch("tools.skills_hub._read_index_cache", return_value=None)
|
||||
@patch("tools.skills_hub.httpx.get")
|
||||
def test_empty_search_uses_featured_homepage_links(self, mock_get, _mock_read_cache, _mock_write_cache):
|
||||
mock_get.return_value = MagicMock(
|
||||
status_code=200,
|
||||
text='''
|
||||
<a href="/vercel-labs/agent-skills/vercel-react-best-practices">React</a>
|
||||
<a href="/anthropics/skills/pdf">PDF</a>
|
||||
<a href="/vercel-labs/agent-skills/vercel-react-best-practices">React again</a>
|
||||
''',
|
||||
)
|
||||
|
||||
results = self._source().search("", limit=10)
|
||||
|
||||
assert [r.identifier for r in results] == [
|
||||
"skills-sh/vercel-labs/agent-skills/vercel-react-best-practices",
|
||||
"skills-sh/anthropics/skills/pdf",
|
||||
]
|
||||
assert all(r.source == "skills.sh" for r in results)
|
||||
|
||||
@patch.object(GitHubSource, "fetch")
|
||||
def test_fetch_delegates_to_github_source_and_relabels_bundle(self, mock_fetch):
|
||||
mock_fetch.return_value = SkillBundle(
|
||||
name="vercel-react-best-practices",
|
||||
files={"SKILL.md": "# Test"},
|
||||
source="github",
|
||||
identifier="vercel-labs/agent-skills/vercel-react-best-practices",
|
||||
trust_level="community",
|
||||
)
|
||||
|
||||
bundle = self._source().fetch("skills-sh/vercel-labs/agent-skills/vercel-react-best-practices")
|
||||
|
||||
assert bundle is not None
|
||||
assert bundle.source == "skills.sh"
|
||||
assert bundle.identifier == "skills-sh/vercel-labs/agent-skills/vercel-react-best-practices"
|
||||
mock_fetch.assert_called_once_with("vercel-labs/agent-skills/vercel-react-best-practices")
|
||||
|
||||
@patch.object(GitHubSource, "inspect")
|
||||
def test_inspect_delegates_to_github_source_and_relabels_meta(self, mock_inspect):
|
||||
mock_inspect.return_value = SkillMeta(
|
||||
name="vercel-react-best-practices",
|
||||
description="React rules",
|
||||
source="github",
|
||||
identifier="vercel-labs/agent-skills/vercel-react-best-practices",
|
||||
trust_level="community",
|
||||
repo="vercel-labs/agent-skills",
|
||||
path="vercel-react-best-practices",
|
||||
)
|
||||
|
||||
meta = self._source().inspect("skills-sh/vercel-labs/agent-skills/vercel-react-best-practices")
|
||||
|
||||
assert meta is not None
|
||||
assert meta.source == "skills.sh"
|
||||
assert meta.identifier == "skills-sh/vercel-labs/agent-skills/vercel-react-best-practices"
|
||||
mock_inspect.assert_called_once_with("vercel-labs/agent-skills/vercel-react-best-practices")
|
||||
|
||||
@patch.object(GitHubSource, "_list_skills_in_repo")
|
||||
@patch.object(GitHubSource, "inspect")
|
||||
def test_inspect_falls_back_to_repo_skill_catalog_when_slug_differs(self, mock_inspect, mock_list_skills):
|
||||
resolved = SkillMeta(
|
||||
name="vercel-react-best-practices",
|
||||
description="React rules",
|
||||
source="github",
|
||||
identifier="vercel-labs/agent-skills/skills/react-best-practices",
|
||||
trust_level="community",
|
||||
repo="vercel-labs/agent-skills",
|
||||
path="skills/react-best-practices",
|
||||
)
|
||||
mock_inspect.side_effect = lambda identifier: resolved if identifier == resolved.identifier else None
|
||||
mock_list_skills.return_value = [resolved]
|
||||
|
||||
meta = self._source().inspect("skills-sh/vercel-labs/agent-skills/vercel-react-best-practices")
|
||||
|
||||
assert meta is not None
|
||||
assert meta.identifier == "skills-sh/vercel-labs/agent-skills/vercel-react-best-practices"
|
||||
assert mock_list_skills.called
|
||||
|
||||
|
||||
class TestCreateSourceRouter:
|
||||
def test_includes_skills_sh_source(self):
|
||||
sources = create_source_router(auth=MagicMock(spec=GitHubAuth))
|
||||
assert any(isinstance(src, SkillsShSource) for src in sources)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HubLockFile
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue