How to Build a Skill
Skills are Python or JavaScript packages that give AI agents new capabilities. This guide walks through building, testing, and publishing one to CrustyClaws.
File Structure
Every skill is a directory with three files. Agents care most about SKILL.md ā write it first.
my-weather-skill/
āāā SKILL.md # Documentation (what it does, inputs, outputs, examples)
āāā plugin.json # Metadata and tool definitions
āāā index.js # Implementation (or index.py for Python)Write SKILL.md
SKILL.md is what AI agents read to understand your skill. Be explicit about inputs, outputs, and any API keys or environment variables required.
---
name: my-weather-skill
version: 1.0.0
---
# My Weather Skill
Fetches current weather data for any city using the OpenWeather API.
## Inputs
- `city` (string, required) ā the city name to look up
## Outputs
Returns a JSON object with `temperature`, `condition`, and `humidity`.
## Notes
Requires `OPENWEATHER_API_KEY` set in your environment.Define plugin.json
plugin.json declares your skill's metadata and tools. The tools array uses the same JSON Schema format as tool-calling LLMs.
{
"name": "my-weather-skill",
"version": "1.0.0",
"description": "Fetch current weather for any city",
"author": "your-github-username",
"entrypoint": "index.js",
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name"
}
},
"required": ["city"]
}
}
]
}nameUnique slug ā lowercase, hyphens only. Becomes the openclaw install <name> command.entrypointindex.js for JavaScript, index.py for Python.toolsTool definitions ā each needs a name, description, and JSON Schema parameters block.Write the Implementation
Export one function per tool. Function names must match the name field in plugin.json.
JavaScript (index.js)
export async function get_weather({ city }) {
const apiKey = process.env.OPENWEATHER_API_KEY
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`
)
if (!res.ok) throw new Error(`Weather API error: ${res.status}`)
const data = await res.json()
return {
temperature: data.main.temp,
condition: data.weather[0].description,
humidity: data.main.humidity,
}
}Python (index.py)
import os
import httpx
async def get_weather(city: str) -> dict:
"""Get current weather for a city."""
api_key = os.environ["OPENWEATHER_API_KEY"]
async with httpx.AsyncClient() as client:
res = await client.get(
"https://api.openweathermap.org/data/2.5/weather",
params={"q": city, "appid": api_key, "units": "metric"},
)
res.raise_for_status()
data = res.json()
return {
"temperature": data["main"]["temp"],
"condition": data["weather"][0]["description"],
"humidity": data["main"]["humidity"],
}Test Locally
Install from a local path before publishing to catch any issues:
openclaw install ./my-weather-skill
openclaw run get_weather --city "London"Publish to CrustyClaws
Two options ā pick whichever fits your workflow:
Option A ā Upload via the web
Go to /upload, fill in title, description, price, and paste your GitHub file URL.
Option B ā Publish via the openclaw/skills repo
Open a PR to the openclaw/skills GitHub repo. CrustyClaws auto-syncs from that repo hourly.
# Fork openclaw/skills, add your skill directory, open a PR
git clone https://github.com/openclaw/skills
cp -r ./my-weather-skill skills/your-username/my-weather-skill
git add . && git commit -m "add my-weather-skill"
git push && gh pr createReady to publish?
Upload a skill and start earning.