뒤로가기

07. OpenClaw Skills 개발 입문: 첫 번째 스킬 만들기

본문은 첫 번째 OpenClaw Skill 개발을 단계별로 안내합니다. SKILL.md 구조 해석부터 완전한 예시까지, 스킬 정의, 트리거 설계, 도구 통합, 테스트 배포 전 과정을 다루어 AI 어시스턴트에 사용자 정의 능력을 추가하는 방법을 소개합니다.

OpenClaw v2026.2 적용 | 본문은 기본 Markdown과 명령줄에 익숙하고 사용자 정의 스킬을 만들고 싶은 사용자를 대상으로 합니다.

TL;DR: Skill 생성에는 SKILL.md 파일 하나만 필요합니다. 핵심 필드: name, version, triggers(트리거), tools(도구 목록). 위치: ~/.openclaw/workspace/skills/<name>/SKILL.md. 트리거는 충돌을 피하기 위해 구체적으로 설계합니다. ClawHub 배포: openclaw skill publish <name>. 보안 스캔 및 권한 제어 포함.

개발 환경 준비

사전 요구사항

요구사항 설명
OpenClaw 설치 및 실행 완료
텍스트 에디터 VS Code 또는 임의 에디터
기본 Markdown SKILL.md는 Markdown 형식 사용
API 문서 외부 서비스 연동 시 API 문서 준비

스킬 디렉토리 생성

# 스킬 디렉토리 생성
mkdir -p ~/.openclaw/workspace/skills/weather-api

# 디렉토리로 이동
cd ~/.openclaw/workspace/skills/weather-api

# 기본 파일 생성
touch SKILL.md
touch README.md
touch config.json.example

디렉토리 구조

weather-api/
├── SKILL.md              # 스킬 정의(필수)
├── README.md             # 설명 문서(권장)
├── config.json.example   # 설정 예시(선택)
├── src/                  # 소스 코드(선택)
│   └── index.js
└── package.json          # 의존성 선언(선택)

SKILL.md 구조 상세

기본 구조

---
# Frontmatter: 메타데이터
name: skill-name
version: 1.0.0
author: your-name
description: Brief description
triggers:
  - trigger-1
  - trigger-2
dependencies: []
tools: []
---

# Skill Title

Detailed description...

## Features
## Setup
## Usage Examples
## Configuration
## Notes

Frontmatter 필드

필드 필수 설명
name 스킬 고유 식별자, 소문자 및 하이픈
version 시맨틱 버전 번호
author 작성자 이름 또는 조직
description 간단한 설명(한 문장)
triggers 트리거 목록
dependencies ⚠️ 의존하는 다른 스킬
tools ⚠️ 제공하는 도구 목록

트리거 설계

트리거는 Agent가 스킬을 인식하는 핵심입니다:

triggers:
  - weather
  - 날씨
  - forecast
  - 气温
  - temperature
  - rain
  - 비
  - umbrella
  - 우산

설계 원칙:

원칙 설명 예시
구체성 과도하게 일반적인 단어 피하기 gmail-sendsend
다국어 한영 지원 weather 날씨
시나리오화 사용 시나리오 포함 check-weather weather-tomorrow
충돌 없음 기존 스킬과 충돌 확인 openclaw skill search weather

실전: 날씨 조회 스킬 개발

요구사항 분석

날씨 조회 스킬을 개발하며 다음 기능을 포함합니다:

  1. 현재 날씨 조회
  2. 향후 며칠 예보 조회
  3. 한영 도시명 지원
  4. 옷차림 추천 제공

완전한 SKILL.md

---
name: weather-api
version: 1.0.0
author: your-name
description: Query weather information from OpenWeatherMap API
triggers:
  - weather
  - 날씨
  - forecast
  - 예보
  - temperature
  - 기온
  - rain
  - 비
  - umbrella
  - 우산
dependencies: []
tools:
  - weather_current
  - weather_forecast
---

# Weather API Skill

Query weather information for any city worldwide. Provides current weather and forecasts with practical suggestions.

## Features

- **Current Weather**: Get real-time weather conditions
- **Forecast**: 5-day weather forecast
- **Multi-language**: Support for city names in multiple languages
- **Smart Suggestions**: Clothing and activity recommendations

## Setup

### 1. Get API Key

1. Visit [OpenWeatherMap](https://openweathermap.org/api)
2. Sign up for a free account
3. Generate an API key

### 2. Configure the Skill

Set the environment variable:

\`\`\`bash
export OPENWEATHER_API_KEY="your-api-key-here"
\`\`\`

Or add to your OpenClaw config:

\`\`\`json
{
  "skills": {
    "weather-api": {
      "apiKey": "your-api-key-here",
      "units": "metric",
      "lang": "ko"
    }
  }
}
\`\`\`

## Usage Examples

### Current Weather

User: "서울 오늘 날씨 어때"
User: "What's the weather in Tokyo"
User: "Check weather for Shanghai"

Action:
1. Parse city name from user message
2. Call weather_current tool with city parameter
3. Format and return weather information

### Weather Forecast

User: "내일 비 올까"
User: "뉴욕 3일 날씨 예보"
User: "주말 날씨 어때"

Action:
1. Parse city name and days from user message
2. Call weather_forecast tool
3. Format forecast data into readable summary

### Smart Suggestions

User: "오늘 우산 필요해"
User: "뭘 입는 게 좋을까"
User: "야외 운동하기 좋은 날씨야"

Action:
1. Get current weather
2. Analyze conditions (rain, temperature, UV)
3. Provide practical suggestions

## Tools

### weather_current

Get current weather for a city.

**Parameters:**
- `city` (string, required): City name, can be in any language
- `units` (string, optional): "metric", "imperial", or "kelvin". Default: "metric"
- `lang` (string, optional): Language code. Default: "ko"

**Returns:**
\`\`\`json
{
  "city": "Seoul",
  "country": "KR",
  "temperature": 25,
  "feels_like": 27,
  "humidity": 65,
  "wind_speed": 12,
  "condition": "Partly cloudy",
  "icon": "02d",
  "suggestion": "Light jacket recommended, no rain expected"
}
\`\`\`

### weather_forecast

Get weather forecast for up to 5 days.

**Parameters:**
- `city` (string, required): City name
- `days` (number, optional): Number of days (1-5). Default: 3
- `units` (string, optional): Temperature units

**Returns:**
\`\`\`json
{
  "city": "Seoul",
  "forecast": [
    {
      "date": "2026-02-27",
      "temp_high": 28,
      "temp_low": 18,
      "condition": "Sunny",
      "rain_chance": 0
    },
    ...
  ]
}
\`\`\`

## Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | string | - | OpenWeatherMap API key |
| `units` | string | "metric" | Temperature units |
| `lang` | string | "ko" | Language for descriptions |
| `cache_ttl` | number | 600 | Cache duration in seconds |

## Error Handling

The skill handles common errors:

- **City not found**: Suggests similar city names
- **API key invalid**: Prompts user to check configuration
- **Rate limit**: Queues request and retries
- **Network error**: Falls back to cached data if available

## Notes

- Free tier allows 1000 requests/day
- City names support Korean: "서울", "부산", "인천"
- Results are cached for 10 minutes by default
- Consider upgrading to paid tier for more frequent updates

도구 구현(선택)

스킬에서 구체적인 작업을 수행해야 한다면 도구 구현을 추가할 수 있습니다.

간단한 방법: 내장 도구 사용

OpenClaw에는 web_searchread 도구가 내장되어 있어 SKILL.md에서 사용 방법을 직접 설명할 수 있습니다:

## Implementation Notes

To get weather data:

1. Use `web_search` tool to find weather API
2. Use `read` tool to fetch JSON response
3. Parse and format the data

Example search query: "weather API Seoul current temperature"

고급 방법: 사용자 정의 도구 스크립트

src/index.js 생성:

// src/index.js
const axios = require('axios');

const OPENWEATHER_BASE_URL = 'https://api.openweathermap.org/data/2.5';

async function weather_current({ city, units = 'metric', lang = 'ko' }, config) {
  const apiKey = config.apiKey || process.env.OPENWEATHER_API_KEY;
  
  if (!apiKey) {
    throw new Error('OpenWeatherMap API key not configured');
  }

  try {
    const response = await axios.get(`${OPENWEATHER_BASE_URL}/weather`, {
      params: {
        q: city,
        appid: apiKey,
        units,
        lang
      }
    });

    const data = response.data;
    
    // Generate suggestion based on conditions
    let suggestion = '';
    if (data.rain || data.weather[0].main === 'Rain') {
      suggestion = 'Bring an umbrella, rain expected';
    } else if (data.main.temp < 15) {
      suggestion = 'Wear warm clothing, temperature is low';
    } else if (data.main.temp > 30) {
      suggestion = 'Stay hydrated, high temperature';
    } else {
      suggestion = 'Pleasant weather, enjoy your day';
    }

    return {
      city: data.name,
      country: data.sys.country,
      temperature: data.main.temp,
      feels_like: data.main.feels_like,
      humidity: data.main.humidity,
      wind_speed: data.wind.speed,
      condition: data.weather[0].description,
      icon: data.weather[0].icon,
      suggestion
    };
  } catch (error) {
    if (error.response?.status === 404) {
      throw new Error(`City "${city}" not found. Please check the spelling.`);
    }
    throw error;
  }
}

async function weather_forecast({ city, days = 3, units = 'metric' }, config) {
  const apiKey = config.apiKey || process.env.OPENWEATHER_API_KEY;
  
  if (!apiKey) {
    throw new Error('OpenWeatherMap API key not configured');
  }

  try {
    const response = await axios.get(`${OPENWEATHER_BASE_URL}/forecast`, {
      params: {
        q: city,
        appid: apiKey,
        units,
        cnt: days * 8 // 8 forecasts per day (every 3 hours)
      }
    });

    const data = response.data;
    
    // Group by day and get daily summary
    const dailyForecasts = {};
    data.list.forEach(item => {
      const date = item.dt_txt.split(' ')[0];
      if (!dailyForecasts[date]) {
        dailyForecasts[date] = {
          temp_high: item.main.temp_max,
          temp_low: item.main.temp_min,
          conditions: [item.weather[0].main],
          rain_chance: item.pop * 100
        };
      } else {
        dailyForecasts[date].temp_high = Math.max(dailyForecasts[date].temp_high, item.main.temp_max);
        dailyForecasts[date].temp_low = Math.min(dailyForecasts[date].temp_low, item.main.temp_min);
        if (!dailyForecasts[date].conditions.includes(item.weather[0].main)) {
          dailyForecasts[date].conditions.push(item.weather[0].main);
        }
        dailyForecasts[date].rain_chance = Math.max(dailyForecasts[date].rain_chance, item.pop * 100);
      }
    });

    return {
      city: data.city.name,
      forecast: Object.entries(dailyForecasts)
        .slice(0, days)
        .map(([date, info]) => ({
          date,
          temp_high: Math.round(info.temp_high),
          temp_low: Math.round(info.temp_low),
          condition: info.conditions.join('/'),
          rain_chance: Math.round(info.rain_chance)
        }))
    };
  } catch (error) {
    if (error.response?.status === 404) {
      throw new Error(`City "${city}" not found. Please check the spelling.`);
    }
    throw error;
  }
}

module.exports = {
  weather_current,
  weather_forecast
};

package.json

{
  "name": "weather-api",
  "version": "1.0.0",
  "description": "Weather API skill for OpenClaw",
  "main": "src/index.js",
  "dependencies": {
    "axios": "^1.6.0"
  },
  "openclaw": {
    "skill": "weather-api",
    "tools": ["weather_current", "weather_forecast"]
  }
}

스킬 테스트

로컬 테스트

# SKILL.md 문법 검증
openclaw skill validate weather-api

# 출력 예시:
# ✅ Frontmatter valid
# ✅ Triggers defined
# ✅ Description present
# ✅ Tools documented

# 스킬 로드
openclaw skill load weather-api

# 스킬 테스트
openclaw agent --message "서울 오늘 날씨 어때"

# 날씨 정보가 표시되어야 함

디버그 모드

# 디버그 로그 활성화
openclaw gateway --verbose --filter skill:weather-api

# 스킬 로드 로그 확인
openclaw logs --skill weather-api

테스트 시나리오

테스트 시나리오 입력 예상 출력
한국 도시 “서울 날씨” 서울 날씨 정보
영문 도시 “Weather in Tokyo” 도쿄 날씨 정보
예보 조회 “내일 비 올까” 내일 날씨 예보
옷차림 추천 “오늘 뭘 입어” 날씨 기반 추천
오류 처리 “xxx날씨” 도시 미발견 안내

스킬 배포

배포 준비

  1. 문서 완성: README.md가 완전한지 확인
  2. 예시 추가: 사용 예시 제공
  3. 충분한 테스트: 다양한 경계 케이스 커버
  4. 민감 정보 제거: API Key 등 제거

GitHub에 배포

# Git 초기화
git init
git add .
git commit -m "Initial release of weather-api skill"

# GitHub에 푸시
git remote add origin https://github.com/your-username/weather-api.git
git push -u origin main

# 릴리스 태그 생성
git tag v1.0.0
git push origin v1.0.0

ClawHub에 제출

# ClawHub에 배포
openclaw skill publish weather-api

# 출력 예시:
# Validating skill...
# ✅ Validation passed
# Publishing to ClawHub...
# ✅ Published successfully!
# 
# Your skill is now available at:
# https://clawhub.com/skills/weather-api

README.md 템플릿

# Weather API Skill for OpenClaw

Query weather information from OpenWeatherMap API.

## Features

- Current weather conditions
- 5-day weather forecast
- Multi-language city support
- Smart clothing suggestions

## Installation

\`\`\`bash
openclaw skill install weather-api
\`\`\`

## Configuration

Set your OpenWeatherMap API key:

\`\`\`bash
export OPENWEATHER_API_KEY="your-api-key"
\`\`\`

Or in OpenClaw config:

\`\`\`json
{
  "skills": {
    "weather-api": {
      "apiKey": "your-api-key"
    }
  }
}
\`\`\`

## Usage

Just ask about the weather:

- "서울 오늘 날씨 어때"
- "What's the weather in Tokyo"
- "내일 비 올까"
- "오늘 우산 필요해"

## Requirements

- OpenWeatherMap API key (free tier: 1000 requests/day)
- Internet connection

## License

MIT

고급 기법

조건부 트리거

컨텍스트에 따라 트리거 여부 결정:

## Advanced Triggers

This skill is triggered when:
1. User mentions weather-related keywords
2. User asks about temperature or conditions
3. User needs outdoor activity suggestions

Not triggered when:
- Weather is mentioned metaphorically ("I'm under the weather")
- User is asking about CPU temperature or other technical metrics

컨텍스트 기억

SKILL.md에 컨텍스트 처리 추가:

## Context Handling

When user asks "What about tomorrow?" after a weather query:
1. Check previous context for city name
2. Use the same city for forecast query

When user asks "And in Seoul?" after querying another city:
1. Parse the new city name
2. Compare weather between both cities

체인 스킬

다른 스킬과 협력:

## Skill Integration

This skill works well with:
- **calendar**: Suggest events based on weather
- **travel**: Provide weather for travel destinations
- **clothing**: Recommend outfits based on temperature

Example workflow:
User: "Planning a trip to Tokyo next week"
1. Travel skill: Parse destination and dates
2. Weather skill: Get Tokyo forecast
3. Calendar skill: Check existing events
4. Combined response: Trip summary with weather

오류 복구

우아한 오류 처리:

## Error Recovery

When API fails:
1. Check cache for recent data
2. Inform user about the issue
3. Offer alternatives:
   - "I couldn't get live weather data. Want me to try again?"
   - "The weather service is temporarily unavailable. Would you like a general forecast?"

When city not found:
1. Suggest similar city names
2. Ask for clarification
3. Use geolocation if available

모범 사례

문서 규범

요구사항 설명
명확한 설명 한 문장으로 스킬 기능 설명
충분한 예시 주요 사용 시나리오 커버
설정 설명 설정 단계 상세 설명
오류 처리 일반적인 오류와 해결 방법 나열
버전 업데이트 CHANGELOG 유지

보안 고려사항

## Security Notes

- Never hardcode API keys in SKILL.md
- Use environment variables or config files
- Validate and sanitize user input
- Rate limit API calls
- Log errors without exposing sensitive data

성능 최적화

## Performance Tips

- Cache API responses (10-minute default)
- Use async operations for multiple requests
- Minimize tool calls by batching
- Use compressed responses when available

요약

Skills 개발의 핵심은:

  • SKILL.md가 핵심: 정의 명확, 설명 정확, 예시 충분
  • 트리거가 관건: 설계 합리, 충돌 회피, 시나리오 커버
  • 도구가 능력: 구현 완전, 오류 처리, 성능 최적화
  • 문서가 다리: 사용자가 스킬을 이해하고 사용하도록 돕기

본문 학습을 통해 Skills 개발 기초를 익혔으며, OpenClaw용 사용자 정의 스킬을 만들 수 있습니다.


본문 요약:

  • SKILL.md 구조와 필드 이해
  • 트리거 설계 원칙 학습
  • 도구 구현 방법 습득
  • 테스트 및 배포 프로세스 이해
  • 고급 기법과 모범 사례 학습

업데이트 기록:

  • 2026-02-26: 초판 발행, OpenClaw v2026.2 기반

시리즈 네비게이션: