-
-
Notifications
You must be signed in to change notification settings - Fork 11.6k
feat(skills): Skills Registry & Hub — register skills, browse in AI Hub, public skill hub #25118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fcbb96c
99f9e3c
56cdaf8
78b6f5a
3a09bf8
687c5b4
3d2e362
d241878
8c8f3aa
2210409
8936086
a2f171c
fff96e9
26f637e
d81e750
6edda32
3e8da35
26ca2d9
1425fa5
8202ffe
f63b45f
5579b82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # Skills Gateway | ||
|
|
||
| <iframe width="840" height="500" src="https://www.loom.com/embed/cb74eb79df3e4c2b83a6efae54a589f9" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> | ||
|
|
||
| LiteLLM acts as a **Skills Registry** — a central place to register, manage, and discover Claude Code skills across your organization. Teams can publish skills once and have agents and developers find them through a single hub. | ||
|
|
||
| ## How it works | ||
|
|
||
| ```mermaid | ||
| graph TD | ||
| Dev["👨💻 Developer<br/>registers a skill<br/>(GitHub URL or subdir)"] -->|POST /claude-code/plugins| Proxy["LiteLLM Proxy<br/>(Skills Registry)"] | ||
|
|
||
| Admin["🔑 Admin<br/>publishes skill<br/>(marks as public)"] -->|enable via UI or API| Proxy | ||
|
|
||
| Proxy -->|GET /public/skill_hub| SkillHub["🗂️ Skill Hub<br/>(AI Hub → Skill Hub tab)"] | ||
| Proxy -->|GET /claude-code/marketplace.json| Marketplace["📦 Claude Code<br/>Marketplace endpoint"] | ||
|
|
||
| SkillHub --> Human["🧑 Human<br/>browses & discovers skills<br/>in AI Hub UI"] | ||
| Marketplace --> Agent["🤖 Agent / Claude Code<br/>installs skill with<br/>/plugin marketplace add <name>"] | ||
|
|
||
| style Proxy fill:#1a73e8,color:#fff | ||
| style SkillHub fill:#e8f0fe,color:#1a73e8 | ||
| style Marketplace fill:#e8f0fe,color:#1a73e8 | ||
| ``` | ||
|
|
||
| ## Quick start | ||
|
|
||
| ### 1. Register a skill | ||
|
|
||
| Paste any GitHub URL into the Skills UI — LiteLLM auto-detects the source type and skill name. | ||
|
|
||
| ```bash | ||
| curl -X POST https://your-proxy/claude-code/plugins \ | ||
| -H "Authorization: Bearer $LITELLM_KEY" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "name": "grill-me", | ||
| "source": { | ||
| "source": "git-subdir", | ||
| "url": "https://github.com/mattpocock/skills", | ||
| "path": "grill-me" | ||
| }, | ||
| "description": "Interview skill for relentless questioning", | ||
| "domain": "Productivity", | ||
| "namespace": "interviews" | ||
| }' | ||
| ``` | ||
|
|
||
| Skills nested in subdirectories (e.g. `github.com/org/repo/tree/main/skill-name`) are supported — LiteLLM parses the URL automatically in the UI. | ||
|
|
||
| ### 2. Publish to hub | ||
|
|
||
| In the Admin UI: **AI Hub → Skill Hub → Select Skills to Make Public**. | ||
|
|
||
| Or via API: | ||
|
|
||
| ```bash | ||
| curl -X POST https://your-proxy/claude-code/plugins/grill-me/enable \ | ||
| -H "Authorization: Bearer $LITELLM_KEY" | ||
| ``` | ||
|
|
||
| ### 3. Browse the hub | ||
|
|
||
| Public skills appear at: | ||
| - **Admin UI**: AI Hub → Skill Hub tab | ||
| - **Public page**: `/ui/model_hub` → Skill Hub tab (no login required) | ||
| - **API**: `GET /public/skill_hub` | ||
|
|
||
| ### 4. Install in Claude Code | ||
|
|
||
| Point Claude Code at your proxy marketplace once: | ||
|
|
||
| ```json title="~/.claude/settings.json" | ||
| { | ||
| "extraKnownMarketplaces": { | ||
| "my-org": { | ||
| "source": "url", | ||
| "url": "https://your-proxy/claude-code/marketplace.json" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Then install any skill: | ||
|
|
||
| ``` | ||
| /plugin marketplace add grill-me | ||
| ``` | ||
|
|
||
| ## Skill fields | ||
|
|
||
| | Field | Description | | ||
| |-------|-------------| | ||
| | `name` | Unique skill identifier (used in `/plugin marketplace add`) | | ||
| | `source` | Git source — `github`, `url`, or `git-subdir` | | ||
| | `description` | Short description shown in the hub | | ||
| | `domain` | Category for grouping (e.g. `Engineering`, `Productivity`) | | ||
| | `namespace` | Subcategory within a domain (e.g. `quality`, `meetings`) | | ||
| | `keywords` | Tags for search and filtering | | ||
| | `version` | Semver string | | ||
|
|
||
| ## API reference | ||
|
|
||
| | Endpoint | Auth | Description | | ||
| |----------|------|-------------| | ||
| | `POST /claude-code/plugins` | Required | Register a skill | | ||
| | `GET /claude-code/plugins` | Required | List all skills (admin) | | ||
| | `POST /claude-code/plugins/{name}/enable` | Required | Publish a skill | | ||
| | `POST /claude-code/plugins/{name}/disable` | Required | Unpublish a skill | | ||
| | `GET /public/skill_hub` | None | List public skills | | ||
| | `GET /claude-code/marketplace.json` | None | Claude Code marketplace manifest | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -247,6 +247,49 @@ async def get_mcp_servers(): | |
| ] | ||
|
|
||
|
|
||
| @router.get( | ||
| "/public/skill_hub", | ||
| tags=["public", "Claude Code Marketplace"], | ||
| ) | ||
| async def public_skill_hub(): | ||
| """Return enabled (public) Claude Code skills — no auth required.""" | ||
| from litellm.proxy.anthropic_endpoints.claude_code_endpoints.claude_code_marketplace import ( | ||
| _get_prisma_client, | ||
| ) | ||
| from litellm.types.proxy.claude_code_endpoints import ListPluginsResponse, PluginListItem | ||
|
|
||
| try: | ||
| prisma_client = await _get_prisma_client() | ||
| plugins = await prisma_client.db.litellm_claudecodeplugintable.find_many( | ||
| where={"enabled": True} | ||
| ) | ||
| items = [] | ||
| for plugin in plugins: | ||
| raw = plugin.manifest_json or {} | ||
| manifest = json.loads(raw) if isinstance(raw, str) else raw | ||
| items.append( | ||
| PluginListItem( | ||
| id=plugin.id, | ||
| name=plugin.name, | ||
| enabled=plugin.enabled, | ||
| created_at=str(plugin.created_at) if plugin.created_at else None, | ||
| updated_at=str(plugin.updated_at) if plugin.updated_at else None, | ||
| source=manifest.get("source", {}), | ||
| description=manifest.get("description"), | ||
| version=manifest.get("version"), | ||
| category=manifest.get("category"), | ||
| keywords=manifest.get("keywords"), | ||
| author=manifest.get("author"), | ||
| homepage=manifest.get("homepage"), | ||
| domain=manifest.get("domain"), | ||
| namespace=manifest.get("namespace"), | ||
| ) | ||
| ) | ||
| return ListPluginsResponse(plugins=items, count=len(items)) | ||
| except Exception as e: | ||
| raise HTTPException(status_code=500, detail=str(e)) | ||
|
Comment on lines
+273
to
+290
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider adding a reasonable Context Used: CLAUDE.md (source) |
||
|
|
||
|
|
||
| @router.get( | ||
| "/public/model_hub/info", | ||
| tags=["public", "model management"], | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CLAUDE.mdsays: "Avoid imports within methods — place all imports at the top of the file (module-level). The only exception is avoiding circular imports where absolutely necessary."Neither import here creates a circular dependency —
public_endpoints.pyalready imports fromlitellm.proxy.auth.user_api_key_auth(which touchesproxy_server) andlitellm.types.*freely at module level, so these two can move up as well.Additionally,
_get_prisma_clientcarries a leading underscore that conventionally marks it as module-private. Importing a private symbol across module boundaries is a design smell; consider exposing a public helper or duplicating the two-line guard inline.Move both lines to the top-level import block alongside the other
litellm.types.*imports already present (lines 20–30).Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!