refactor: remove the claude-marketplace skill source (redundant Marketplace hub tab)

The Skills Hub 'Marketplace' tab showed a single useless entry: Anthropic
changed .claude-plugin/marketplace.json to bundle-shaped plugins whose
source is './', so all plugins collapsed to one identifier pointing at the
repo root, and the second marketplace repo (aiskillstore/marketplace) is
gone (404). Everything in anthropics/skills is already surfaced by the
GitHub tap as the Anthropic tab, making this source fully redundant.

Removes ClaudeMarketplaceSource and all wiring: source router, index
builder (crawl + floors + sort order + rate-limit messaging), extract
labels/install/URL mapping, hub UI tab, web server labels, CLI limits,
docs (en + zh), the legacy index-cache snapshot, and test fixtures.

Stale skills-index entries with source 'claude-marketplace' still install
fine: HermesIndexSource fetches via resolved GitHub paths generically.
This commit is contained in:
Teknium
2026-07-28 22:42:40 -07:00
parent 7c6caac160
commit 158e9a9977
10 changed files with 26 additions and 173 deletions
+2 -2
View File
@@ -361,7 +361,7 @@ def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
_PER_SOURCE_LIMIT = {
"hermes-index": 1000000,
"official": 200, "skills-sh": 200, "well-known": 50,
"github": 200, "clawhub": 500, "claude-marketplace": 100,
"github": 200, "clawhub": 500,
"lobehub": 500, "browse-sh": 500,
}
@@ -871,7 +871,7 @@ def browse_skills(page: int = 1, page_size: int = 20, source: str = "all") -> di
# low cap here silently truncates the whole hub (see do_browse note).
_PER_SOURCE_LIMIT = {"hermes-index": 5000, "official": 100, "skills-sh": 100,
"well-known": 25, "github": 100, "clawhub": 50,
"claude-marketplace": 50, "lobehub": 50, "browse-sh": 500}
"lobehub": 50, "browse-sh": 500}
auth = GitHubAuth()
sources = create_source_router(auth)
# Delegate to the shared parallel walker so this inherits the index-aware
+1 -2
View File
@@ -14761,7 +14761,6 @@ _SKILL_HUB_SOURCE_LABELS = {
"url": "Direct URL",
"github": "GitHub",
"clawhub": "ClawHub",
"claude-marketplace": "Claude Marketplace",
"lobehub": "LobeHub",
"browse-sh": "browse.sh",
}
@@ -14863,7 +14862,7 @@ async def list_skills_hub_sources(profile: Optional[str] = None):
# calls per keystroke. Keep this set in sync with that function's
# ``_api_source_ids``.
_api_source_ids = frozenset(
{"github", "skills-sh", "clawhub", "claude-marketplace", "lobehub", "well-known"}
{"github", "skills-sh", "clawhub", "lobehub", "well-known"}
)
for entry in out:
entry["searchable"] = not (index_available and entry["id"] in _api_source_ids)
+7 -10
View File
@@ -2,7 +2,7 @@
"""Build the Hermes Skills Index — a centralized JSON catalog of all skills.
This script crawls every skill source (skills.sh, GitHub taps, official,
clawhub, lobehub, claude-marketplace) and writes a JSON index with resolved
clawhub, lobehub) and writes a JSON index with resolved
GitHub paths. The index is served as a static file on the docs site so that
`hermes skills search/install` can use it without hitting the GitHub API.
@@ -38,7 +38,6 @@ from tools.skills_hub import (
OptionalSkillSource,
WellKnownSkillSource,
ClawHubSource,
ClaudeMarketplaceSource,
LobeHubSource,
BrowseShSource,
SkillMeta,
@@ -256,7 +255,6 @@ def main():
"well-known": WellKnownSkillSource(),
"github": GitHubSource(auth=auth),
"clawhub": ClawHubSource(),
"claude-marketplace": ClaudeMarketplaceSource(auth=auth),
"lobehub": LobeHubSource(),
"browse-sh": BrowseShSource(),
}
@@ -277,7 +275,6 @@ def main():
"clawhub": 0,
"lobehub": 100_000,
"browse-sh": 5_000,
"claude-marketplace": 5_000,
"github": 5_000,
"well-known": 5_000,
"official": 5_000,
@@ -299,9 +296,9 @@ def main():
all_skills = batch_resolve_paths(all_skills, auth)
# Collect which sources hit a GitHub API rate limit during the crawl.
# github / claude-marketplace / well-known all read api.github.com, so a
# rate-limited token zeroes all three at once — surfaced below so the
# failure message names the real cause instead of "source returned 0".
# github / well-known both read api.github.com, so a rate-limited token
# zeroes both at once — surfaced below so the failure message names the
# real cause instead of "source returned 0".
rate_limited_sources = {
name for name, source in sources.items()
if getattr(source, "is_rate_limited", False)
@@ -324,7 +321,7 @@ def main():
# Sort
source_order = {"official": 0, "skills-sh": 1, "skills.sh": 1,
"github": 2, "well-known": 3, "clawhub": 4,
"browse-sh": 5, "claude-marketplace": 6, "lobehub": 7}
"browse-sh": 5, "lobehub": 6}
deduped.sort(key=lambda s: (source_order.get(s["source"], 99), s["name"]))
from collections import Counter
@@ -385,8 +382,8 @@ def main():
"\nGitHub API rate limit was hit during this crawl for: "
+ ", ".join(sorted(rate_limited_sources))
+ ". This is the usual cause of an all-GitHub-tap collapse "
"(github / claude-marketplace / well-known dropping to zero "
"together). Re-run with a higher-quota GITHUB_TOKEN.",
"(github / well-known dropping to zero together). "
"Re-run with a higher-quota GITHUB_TOKEN.",
file=sys.stderr,
)
print(
@@ -1 +0,0 @@
[{"name": "document-skills", "description": "Collection of document processing suite including Excel, Word, PowerPoint, and PDF capabilities", "source": "./", "strict": false, "skills": ["./skills/xlsx", "./skills/docx", "./skills/pptx", "./skills/pdf"]}, {"name": "example-skills", "description": "Collection of example skills demonstrating various capabilities including skill creation, MCP building, visual design, algorithmic art, internal communications, web testing, artifact building, Slack GIFs, and theme styling", "source": "./", "strict": false, "skills": ["./skills/algorithmic-art", "./skills/brand-guidelines", "./skills/canvas-design", "./skills/doc-coauthoring", "./skills/frontend-design", "./skills/internal-comms", "./skills/mcp-builder", "./skills/skill-creator", "./skills/slack-gif-creator", "./skills/theme-factory", "./skills/web-artifacts-builder", "./skills/webapp-testing"]}]
@@ -1,7 +1,7 @@
"""Invariants for scripts/build_skills_index.py's health-check guard.
Regression context (June 2026): a GitHub API rate limit zeroed every
api.github.com-backed source (github / claude-marketplace / well-known) at
api.github.com-backed source (github / well-known) at
once during the docs deploy crawl. The build's health check fired and exited
non-zero — but it had ALREADY written the degenerate index to disk, and
deploy-site.yml swallowed the exit code with ``|| echo non-fatal``. The
@@ -40,7 +40,7 @@ class _FakeSource:
return [_meta(f"{self._src}-{i}", self._src) for i in range(self._n)]
def _install_fake_sources(monkeypatch, *, github_count, claude_count=40,
def _install_fake_sources(monkeypatch, *, github_count,
well_known_count=10, github_rate_limited=False):
monkeypatch.setattr(build_mod, "SkillsShSource", lambda auth: _FakeSource("skills.sh", 15000))
monkeypatch.setattr(build_mod, "OptionalSkillSource", lambda: _FakeSource("official", 95))
@@ -50,10 +50,6 @@ def _install_fake_sources(monkeypatch, *, github_count, claude_count=40,
lambda auth: _FakeSource("github", github_count, rate_limited=github_rate_limited),
)
monkeypatch.setattr(build_mod, "ClawHubSource", lambda: _FakeSource("clawhub", 69000))
monkeypatch.setattr(
build_mod, "ClaudeMarketplaceSource",
lambda auth: _FakeSource("claude-marketplace", claude_count, rate_limited=github_rate_limited),
)
monkeypatch.setattr(build_mod, "LobeHubSource", lambda: _FakeSource("lobehub", 500))
monkeypatch.setattr(build_mod, "BrowseShSource", lambda: _FakeSource("browse-sh", 380))
monkeypatch.setattr(
@@ -71,7 +67,7 @@ def test_degenerate_crawl_exits_nonzero_and_writes_no_file(tmp_path, monkeypatch
"""A collapsed GitHub crawl must fail loud and leave OUTPUT_PATH unwritten."""
out = tmp_path / "skills-index.json"
monkeypatch.setattr(build_mod, "OUTPUT_PATH", str(out))
_install_fake_sources(monkeypatch, github_count=0, claude_count=0,
_install_fake_sources(monkeypatch, github_count=0,
well_known_count=0, github_rate_limited=True)
with pytest.raises(SystemExit) as exc:
@@ -95,5 +91,5 @@ def test_healthy_crawl_writes_index_with_all_sources(tmp_path, monkeypatch):
data = json.loads(out.read_text())
sources = {s["source"] for s in data["skills"]}
# Every GitHub-API-backed source that vanished in the regression is present.
assert {"github", "claude-marketplace", "well-known"} <= sources
assert {"github", "well-known"} <= sources
assert data["skill_count"] == len(data["skills"])
+3 -108
View File
@@ -132,7 +132,7 @@ class SkillMeta:
"""Minimal metadata returned by search results."""
name: str
description: str
source: str # "official", "github", "clawhub", "claude-marketplace", "lobehub"
source: str # "official", "github", "clawhub", "lobehub"
identifier: str # source-specific ID (e.g. "openai/skills/skill-creator")
trust_level: str # "builtin" | "trusted" | "community"
repo: Optional[str] = None
@@ -876,7 +876,7 @@ class GitHubSource(SkillSource):
- 403/429 with ``X-RateLimit-Remaining: 0`` — waits until the
reset time (capped) when the header is present, else exponential
backoff. This is the all-GitHub-tap-collapse case: a single
shared rate limit zeroes github + claude-marketplace + well-known
shared rate limit zeroes github + well-known
at once during the index build.
- 5xx and connection/timeout errors — exponential backoff.
@@ -2724,110 +2724,6 @@ class ClawHubSource(SkillSource):
return None
# ---------------------------------------------------------------------------
# Claude Code marketplace source adapter
# ---------------------------------------------------------------------------
class ClaudeMarketplaceSource(SkillSource):
"""
Discover skills from Claude Code marketplace repos.
Marketplace repos contain .claude-plugin/marketplace.json with plugin listings.
"""
KNOWN_MARKETPLACES = [
"anthropics/skills",
"aiskillstore/marketplace",
]
def __init__(self, auth: GitHubAuth):
self.auth = auth
# Persistent GitHubSource so rate-limit state survives across the
# marketplace-index fetch + per-skill inspect calls and can be
# surfaced to the index builder (see is_rate_limited).
self.github = GitHubSource(auth=auth)
def source_id(self) -> str:
return "claude-marketplace"
@property
def is_rate_limited(self) -> bool:
"""Whether the underlying GitHub API hit a rate limit during the crawl."""
return self.github.is_rate_limited
def trust_level_for(self, identifier: str) -> str:
parts = identifier.split("/", 2)
if len(parts) >= 2:
repo = f"{parts[0]}/{parts[1]}"
if repo in TRUSTED_REPOS:
return "trusted"
return "community"
def search(self, query: str, limit: int = 10) -> List[SkillMeta]:
results: List[SkillMeta] = []
query_lower = query.lower()
for marketplace_repo in self.KNOWN_MARKETPLACES:
plugins = self._fetch_marketplace_index(marketplace_repo)
for plugin in plugins:
searchable = f"{plugin.get('name', '')} {plugin.get('description', '')}".lower()
if query_lower in searchable:
source_path = plugin.get("source", "")
if source_path.startswith("./"):
identifier = f"{marketplace_repo}/{source_path[2:]}"
elif "/" in source_path:
identifier = source_path
else:
identifier = f"{marketplace_repo}/{source_path}"
results.append(SkillMeta(
name=plugin.get("name", ""),
description=plugin.get("description", ""),
source="claude-marketplace",
identifier=identifier,
trust_level=self.trust_level_for(identifier),
repo=marketplace_repo,
))
return results[:limit]
def fetch(self, identifier: str) -> Optional[SkillBundle]:
# Delegate to GitHub Contents API since marketplace skills live in GitHub repos
bundle = self.github.fetch(identifier)
if bundle:
bundle.source = "claude-marketplace"
return bundle
def inspect(self, identifier: str) -> Optional[SkillMeta]:
meta = self.github.inspect(identifier)
if meta:
meta.source = "claude-marketplace"
meta.trust_level = self.trust_level_for(identifier)
return meta
def _fetch_marketplace_index(self, repo: str) -> List[dict]:
"""Fetch and parse .claude-plugin/marketplace.json from a repo."""
cache_key = f"claude_marketplace_{repo.replace('/', '_')}"
cached = _read_index_cache(cache_key)
if cached is not None:
return cached
url = f"https://api.github.com/repos/{repo}/contents/.claude-plugin/marketplace.json"
resp = self.github._github_get(
url,
headers={**self.auth.get_headers(), "Accept": "application/vnd.github.v3.raw"},
)
if resp is None or resp.status_code != 200:
return []
try:
data = json.loads(resp.text)
except json.JSONDecodeError:
return []
plugins = data.get("plugins", [])
_write_index_cache(cache_key, plugins)
return plugins
# ---------------------------------------------------------------------------
# LobeHub source adapter
# ---------------------------------------------------------------------------
@@ -4102,7 +3998,6 @@ def create_source_router(auth: Optional[GitHubAuth] = None) -> List[SkillSource]
UrlSource(), # Direct HTTP(S) URL to a SKILL.md file
GitHubSource(auth=auth, extra_taps=extra_taps),
ClawHubSource(),
ClaudeMarketplaceSource(auth=auth),
LobeHubSource(),
BrowseShSource(), # browse.sh: 169+ site-specific browser automation skills
]
@@ -4156,7 +4051,7 @@ def parallel_search_sources(
# ~70 GitHub API calls per search for unauthenticated users.
_index_available = False
_api_source_ids = frozenset({"github", "skills-sh", "clawhub",
"claude-marketplace", "lobehub", "well-known"})
"lobehub", "well-known"})
if _effective_filter == "all":
for src in sources:
if (src.source_id() == "hermes-index"
+3 -13
View File
@@ -640,17 +640,7 @@ A third-party skills marketplace integrated as a community source.
- Site: [clawhub.ai](https://clawhub.ai/)
- Hermes source id: `clawhub`
#### 6. Claude marketplace-style repos (`claude-marketplace`)
Hermes supports marketplace repos that publish Claude-compatible plugin/marketplace manifests.
Known integrated sources include:
- [anthropics/skills](https://github.com/anthropics/skills)
- [aiskillstore/marketplace](https://github.com/aiskillstore/marketplace)
Hermes source id: `claude-marketplace`
#### 7. LobeHub (`lobehub`)
#### 6. LobeHub (`lobehub`)
Hermes can search and convert agent entries from LobeHub's public catalog into installable Hermes skills.
@@ -659,7 +649,7 @@ Hermes can search and convert agent entries from LobeHub's public catalog into i
- Backing repo: [lobehub/lobe-chat-agents](https://github.com/lobehub/lobe-chat-agents)
- Hermes source id: `lobehub`
#### 8. browse.sh (`browse-sh`)
#### 7. browse.sh (`browse-sh`)
Hermes integrates with [browse.sh](https://browse.sh), Browserbase's catalog of 200+ site-specific browser-automation SKILL.md files (Airbnb, Amazon, arXiv, 12306.cn, Etsy, Xero, and many more). Each skill describes how to drive one website end-to-end and is suitable for use with Hermes' browser tools and any browser-automation skills you already have installed.
@@ -676,7 +666,7 @@ hermes skills install browse-sh/airbnb.com/search-listings-ddgioa
Identifiers use the form `browse-sh/<hostname>/<task-id>` and match the slug exposed by the browse.sh catalog. Content is resolved through the per-skill detail endpoint (`/api/skills/<slug>` → `skillMdUrl`), not through the catalog's GitHub `sourceUrl`.
#### 9. Direct URL (`url`)
#### 8. Direct URL (`url`)
Install `SKILL.md` directly from any HTTP(S) URL — useful when an author hosts a skill on their own site (no hub listing, no GitHub path to type). Hermes also fetches explicitly referenced files under `references/`, `templates/`, `scripts/`, `assets/`, and `examples/`, then scans and installs the complete bundle.
@@ -414,7 +414,7 @@ hermes skills tap add myorg/skills-repo # Add a custom GitHub source
| `well-known` | `well-known:https://mintlify.com/docs/.well-known/skills/mintlify` | 直接从网站的 `/.well-known/skills/index.json` 提供的 skills。使用站点或文档 URL 搜索。 |
| `url` | `https://sharethis.chat/SKILL.md` | 指向 `SKILL.md` 及其明确引用的支持文件的直接 HTTP(S) URL。名称解析顺序:frontmatter → URL slug → 交互式提示 → `--name` 标志。 |
| `github` | `openai/skills/k8s` | 直接从 GitHub 仓库/路径安装以及基于 GitHub 的自定义 tap。 |
| `clawhub`、`lobehub`、`browse-sh`、`claude-marketplace` | 来源特定标识符 | 社区或市场集成。 |
| `clawhub`、`lobehub`、`browse-sh` | 来源特定标识符 | 社区或市场集成。 |
### 集成的 hub 和注册表
@@ -488,17 +488,7 @@ hermes skills tap add myorg/skills-repo
- 站点:[clawhub.ai](https://clawhub.ai/)
- Hermes 来源 id:`clawhub`
#### 6. Claude 市场式仓库(`claude-marketplace`)
Hermes 支持发布 Claude 兼容插件/市场清单的市场仓库。
已知集成来源包括:
- [anthropics/skills](https://github.com/anthropics/skills)
- [aiskillstore/marketplace](https://github.com/aiskillstore/marketplace)
Hermes 来源 id:`claude-marketplace`
#### 7. LobeHub(`lobehub`)
#### 6. LobeHub(`lobehub`)
Hermes 可以从 LobeHub 的公共目录中搜索并将 agent 条目转换为可安装的 Hermes skills。
@@ -507,7 +497,7 @@ Hermes 可以从 LobeHub 的公共目录中搜索并将 agent 条目转换为可
- 后端仓库:[lobehub/lobe-chat-agents](https://github.com/lobehub/lobe-chat-agents)
- Hermes 来源 id:`lobehub`
#### 8. browse.sh(`browse-sh`)
#### 7. browse.sh(`browse-sh`)
Hermes 与 [browse.sh](https://browse.sh) 集成,这是 Browserbase 的目录,包含 200+ 个针对特定站点的浏览器自动化 SKILL.md 文件(Airbnb、Amazon、arXiv、12306.cn、Etsy、Xero 等)。每个 skill 描述如何端到端驱动一个网站,适合与 Hermes 的浏览器工具以及你已安装的任何浏览器自动化 skills 配合使用。
@@ -524,7 +514,7 @@ hermes skills install browse-sh/airbnb.com/search-listings-ddgioa
标识符使用 `browse-sh/<hostname>/<task-id>` 的形式,与 browse.sh 目录公开的 slug 匹配。内容通过每个 skill 的详情端点(`/api/skills/<slug>` → `skillMdUrl`)解析,而不是通过目录的 GitHub `sourceUrl`。
#### 9. 直接 URL(`url`)
#### 8. 直接 URL(`url`)
直接从任何 HTTP(S) URL 安装 `SKILL.md`——当作者在自己的站点上托管 skill 时非常有用(无 hub 列表,无需输入 GitHub 路径)。Hermes 还会获取其中明确引用且位于 `references/`、`templates/`、`scripts/`、`assets/` 和 `examples/` 下的文件,然后扫描并安装完整捆绑包。
+2 -7
View File
@@ -10,7 +10,7 @@ Two data sources:
2. The unified Hermes Skills Index at ``website/static/api/skills-index.json``,
built twice daily by ``scripts/build_skills_index.py`` (workflow
``.github/workflows/skills-index.yml``). Covers skills.sh, ClawHub, browse.sh,
LobeHub, Claude Marketplace, well-known endpoints, and the GitHub taps
LobeHub, well-known endpoints, and the GitHub taps
(openai/skills, anthropics/skills, huggingface/skills, VoltAgent, etc.).
Legacy fallback: if the unified index is missing AND ``skills/index-cache/``
@@ -82,7 +82,6 @@ UNIFIED_SOURCE_LABELS = {
"clawhub": "ClawHub",
"browse-sh": "browse.sh",
"lobehub": "LobeHub",
"claude-marketplace": "Claude Marketplace",
"well-known": "Well-Known",
"github": "GitHub", # default for non-named GitHub taps
}
@@ -106,7 +105,6 @@ GITHUB_TAP_LABELS = {
LEGACY_SOURCE_LABELS = {
"anthropics_skills": "Anthropic",
"openai_skills": "OpenAI",
"claude_marketplace": "Claude Marketplace",
"lobehub": "LobeHub",
}
@@ -184,8 +182,6 @@ def _install_command(source: str, identifier: str, name: str) -> str:
return f"hermes skills install {identifier}"
if src == "lobehub":
return f"hermes skills install {identifier}"
if src == "claude-marketplace":
return f"hermes skills install {identifier}"
if src == "github":
return f"hermes skills install {identifier}"
if src == "well-known":
@@ -215,8 +211,7 @@ def _source_url(source: str, identifier: str, extra: dict) -> str:
# GitHub-backed taps (openai/anthropic/nvidia/hf/gstack/VoltAgent/...):
# identifier is "owner/repo/<path...>" — link to the directory on GitHub.
if src in {"github", "openai", "anthropic", "huggingface", "nvidia",
"gstack", "voltagent", "minimax", "claude marketplace",
"claude-marketplace"}:
"gstack", "voltagent", "minimax"}:
parts = [p for p in identifier.split("/") if p]
if len(parts) >= 2:
owner, repo = parts[0], parts[1]
-8
View File
@@ -125,13 +125,6 @@ const SOURCE_CONFIG: Record<
border: "rgba(96, 165, 250, 0.2)",
icon: "\u{25CB}",
},
"Claude Marketplace": {
label: "Marketplace",
color: "#a78bfa",
bg: "rgba(167, 139, 250, 0.08)",
border: "rgba(167, 139, 250, 0.2)",
icon: "\u{25A0}",
},
"skills.sh": {
label: "skills.sh",
color: "#34d399",
@@ -223,7 +216,6 @@ const SOURCE_ORDER = [
"ClawHub",
"browse.sh",
"LobeHub",
"Claude Marketplace",
"VoltAgent",
"Well-Known",
"GitHub",