Skip to content

Commit 1aa85ed

Browse files
Clarify mixed marketplace filtering docs
Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 40b1924 commit 1aa85ed

1 file changed

Lines changed: 39 additions & 17 deletions

File tree

sdk/guides/mixed-marketplace-skills.mdx

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This guide focuses on the two loader APIs used in the example:
2525

2626
## Example repository layout
2727

28-
The example repository separates local skills from the marketplace configuration that filters public skills:
28+
The example repository separates local skills from the marketplace configuration that describes which remote skills should be included:
2929

3030
```text
3131
43_mixed_marketplace_skills/
@@ -40,11 +40,34 @@ The example repository separates local skills from the marketplace configuration
4040

4141
## Marketplace format note
4242

43-
The `.plugin/marketplace.json` file follows the Claude Code plugin marketplace schema. In OpenHands, plugin entry names are used as a filter list for which public skills to load from OpenHands/extensions, while local skills live in `local_skills/` and are merged separately.
43+
The `.plugin/marketplace.json` file follows the Claude Code plugin marketplace schema, with an OpenHands extension for direct `skills[]` entries. A minimal mixed example looks like this:
44+
45+
```json
46+
{
47+
"name": "mixed-skills-marketplace",
48+
"plugins": [],
49+
"skills": [
50+
{
51+
"name": "greeting-helper",
52+
"source": "./local_skills/greeting-helper",
53+
"description": "A local greeting skill"
54+
},
55+
{
56+
"name": "github",
57+
"source": "https://github.com/OpenHands/extensions/blob/main/skills/github",
58+
"description": "GitHub best practices from OpenHands/extensions"
59+
}
60+
]
61+
}
62+
```
63+
64+
That file is the repository-managed configuration that expresses the mixed marketplace itself.
4465

45-
The guide below starts with the simplest direct loader calls (`load_skills_from_dir()` and `load_public_skills()`) so you can see exactly what each source contributes. The example repository still includes `.plugin/marketplace.json` because that is the configuration file used for repository-managed marketplace filtering.
66+
The code example below focuses on the two underlying loader APIs (`load_skills_from_dir()` and `load_public_skills()`) so you can see exactly what each source contributes. In other words:
4667

47-
Additionally, OpenHands extends the schema with an optional `skills[]` array for listing skills directly (these are treated as direct skill sources, not plugin bundles).
68+
- `load_skills_from_dir(local_skills_dir)` reads local `SKILL.md` files from your repository.
69+
- `load_public_skills()` reads from the SDK's default OpenHands/extensions marketplace.
70+
- `.plugin/marketplace.json` is the place where you would encode an equivalent curated mix when you want the repository to own that marketplace configuration.
4871

4972
## Example: Combining Local and Remote Skills
5073

@@ -78,9 +101,9 @@ Directory Structure:
78101
├── main.py # This example script
79102
└── README.md
80103
81-
The marketplace.json lists which remote skills to include. In OpenHands, entries
82-
in `skills[]` or `plugins[]` should point directly to skill directories containing
83-
`SKILL.md`; local skills live in `local_skills/` and are loaded separately.
104+
The `.plugin/marketplace.json` file shown earlier is where you would encode the
105+
mixed marketplace itself. In this example script we intentionally call the loader
106+
APIs directly so you can see each source in isolation before combining them.
84107
"""
85108

86109
import argparse
@@ -144,8 +167,9 @@ def main():
144167

145168
print("\nLoading public skills from https://github.com/OpenHands/extensions...")
146169

147-
# Load public skills from the OpenHands extensions repository
148-
# This pulls from the default marketplace at OpenHands/extensions
170+
# Load public skills from the OpenHands extensions repository.
171+
# This call uses the SDK's default OpenHands/extensions marketplace,
172+
# not the `.plugin/marketplace.json` file shown above.
149173
public_skills = load_public_skills()
150174

151175
print(f"\nLoaded {len(public_skills)} public skills from OpenHands/extensions:")
@@ -162,17 +186,15 @@ def main():
162186
print("Part 3: Combining Local and Remote Skills")
163187
print("=" * 80)
164188

165-
# Combine skills with local skills taking precedence
166-
# This allows local skills to override public skills with the same name
189+
# Combine skills with local skills taking precedence.
167190
combined_skills = list(local_skills.values()) + public_skills
168191

169-
# Remove duplicates (keep first occurrence = local takes precedence)
170-
seen_names: set[str] = set()
171-
unique_skills = []
192+
# Use dict insertion order to keep the first occurrence of each skill name,
193+
# which means local skills win over public skills with the same name.
194+
unique_skills_by_name = {}
172195
for skill in combined_skills:
173-
if skill.name not in seen_names:
174-
seen_names.add(skill.name)
175-
unique_skills.append(skill)
196+
unique_skills_by_name.setdefault(skill.name, skill)
197+
unique_skills = list(unique_skills_by_name.values())
176198

177199
print(f"\nTotal combined skills: {len(unique_skills)}")
178200
print(f" - Local skills: {len(local_skills)}")

0 commit comments

Comments
 (0)