Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fcbb96c
feat(skills): add domain and namespace fields to plugin types
ishaan-berri Apr 4, 2026
99f9e3c
feat(skills): store and return domain/namespace inside manifest_json
ishaan-berri Apr 4, 2026
56cdaf8
feat(skills): add /public/skill_hub endpoint for unauthenticated access
ishaan-berri Apr 4, 2026
78b6f5a
feat(skills): whitelist /public/skill_hub from auth requirements
ishaan-berri Apr 4, 2026
3a09bf8
feat(skills): add domain, namespace to Plugin and RegisterPluginReque…
ishaan-berri Apr 4, 2026
687c5b4
feat(skills): smart URL parser — paste github URL, auto-detect source…
ishaan-berri Apr 4, 2026
3d2e362
feat(skills): replace enable toggle with Public badge, make rows clic…
ishaan-berri Apr 4, 2026
d241878
feat(skills): add skill detail view with Overview and How to Use tabs
ishaan-berri Apr 4, 2026
8c8f3aa
feat(skills): add MakeSkillPublicForm modal for publishing skills to …
ishaan-berri Apr 4, 2026
2210409
feat(skills): rename panel to Skills, wire in skill detail view on ro…
ishaan-berri Apr 4, 2026
8936086
feat(skills): add skill hub table columns — name, description, domain…
ishaan-berri Apr 4, 2026
a2f171c
feat(skills): add SkillHubDashboard with stats row, domain dropdown f…
ishaan-berri Apr 4, 2026
fff96e9
feat(skills): add Skill Hub tab to AI Hub with Select Skills to Make …
ishaan-berri Apr 4, 2026
26f637e
feat(skills): move Skills to top-level nav item directly under MCP Se…
ishaan-berri Apr 4, 2026
d81e750
feat(skills): add skillHubPublicCall and NEXT_PUBLIC_BASE_URL support
ishaan-berri Apr 4, 2026
6edda32
feat(skills): add Skill Hub tab to public AI Hub page
ishaan-berri Apr 4, 2026
3e8da35
feat(skills): add skills page routing in main app router
ishaan-berri Apr 4, 2026
26ca2d9
feat(skills): add /skills page route
ishaan-berri Apr 4, 2026
1425fa5
chore: update package-lock after npm install
ishaan-berri Apr 4, 2026
8202ffe
docs(skills): add Skills Gateway doc page with mermaid architecture d…
ishaan-berri Apr 4, 2026
f63b45f
docs(skills): add Skills Gateway to sidebar under Agent & MCP Gateway
ishaan-berri Apr 4, 2026
5579b82
docs(skills): add loom walkthrough video to Skills Gateway doc
ishaan-berri Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions docs/my-website/docs/skills_gateway.md
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 &lt;name&gt;"]

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 |
7 changes: 7 additions & 0 deletions docs/my-website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ const sidebars = {
},
],
},
{
type: "category",
label: "Skills Gateway",
items: [
"skills_gateway",
],
},
],
},
{
Expand Down
1 change: 1 addition & 0 deletions litellm/proxy/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ class LiteLLMRoutes(enum.Enum):
"/public/model_hub",
"/public/agent_hub",
"/public/mcp_hub",
"/public/skill_hub",
"/public/litellm_model_cost_map",
]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ async def register_plugin(
manifest["keywords"] = request.keywords
if request.category:
manifest["category"] = request.category
if request.domain:
manifest["domain"] = request.domain
if request.namespace:
manifest["namespace"] = request.namespace

# Check if plugin exists
existing = await prisma_client.db.litellm_claudecodeplugintable.find_unique(
Expand Down Expand Up @@ -362,6 +366,8 @@ async def list_plugins(
homepage=manifest.get("homepage"),
keywords=manifest.get("keywords"),
category=manifest.get("category"),
domain=manifest.get("domain"),
namespace=manifest.get("namespace"),
enabled=p.enabled,
created_at=p.created_at.isoformat() if p.created_at else None,
updated_at=p.updated_at.isoformat() if p.updated_at else None,
Expand Down
43 changes: 43 additions & 0 deletions litellm/proxy/public_endpoints/public_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Comment on lines +255 to +260

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Inline imports violate project style guide

CLAUDE.md says: "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.py already imports from litellm.proxy.auth.user_api_key_auth (which touches proxy_server) and litellm.types.* freely at module level, so these two can move up as well.

Additionally, _get_prisma_client carries 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!

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No pagination guard on unbounded find_many

CLAUDE.md states: "Bound large result sets. Prisma materializes full results in memory."

find_many(where={"enabled": True}) has no take limit. If an organisation registers thousands of skills, every call to the public hub endpoint will load them all into memory. The other public hub endpoints (/public/agent_hub, /public/mcp_hub) have the same pattern, but that doesn't make it correct here.

Consider adding a reasonable take cap (e.g. 500) and documenting it, or at minimum noting it as a known limitation in a follow-up ticket.

Context Used: CLAUDE.md (source)



@router.get(
"/public/model_hub/info",
tags=["public", "model management"],
Expand Down
4 changes: 4 additions & 0 deletions litellm/types/proxy/claude_code_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class RegisterPluginRequest(BaseModel):
homepage: Optional[str] = Field(None, description="Plugin homepage URL")
keywords: Optional[List[str]] = Field(None, description="Search keywords")
category: Optional[str] = Field(None, description="Plugin category")
domain: Optional[str] = Field(None, description="Skill domain (e.g., 'Productivity')")
namespace: Optional[str] = Field(None, description="Skill namespace within domain (e.g., 'workflows')")


class PluginResponse(BaseModel):
Expand Down Expand Up @@ -82,6 +84,8 @@ class PluginListItem(BaseModel):
homepage: Optional[str] = None
keywords: Optional[List[str]] = None
category: Optional[str] = None
domain: Optional[str] = None
namespace: Optional[str] = None
enabled: bool
created_at: Optional[str]
updated_at: Optional[str]
Expand Down
Loading
Loading