一键导入
my-route-planner
출발지/도착지 경로를 검색하고 소요시간을 역산하여 Google Calendar에 이동 일정을 자동 추가한다. 네이버 Directions 5(자동차) + ODsay(대중교통) API 사용. "이동경로", "경로 추가", "길찾기", "route" 요청에 사용.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
출발지/도착지 경로를 검색하고 소요시간을 역산하여 Google Calendar에 이동 일정을 자동 추가한다. 네이버 Directions 5(자동차) + ODsay(대중교통) API 사용. "이동경로", "경로 추가", "길찾기", "route" 요청에 사용.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | my-route-planner |
| description | 출발지/도착지 경로를 검색하고 소요시간을 역산하여 Google Calendar에 이동 일정을 자동 추가한다. 네이버 Directions 5(자동차) + ODsay(대중교통) API 사용. "이동경로", "경로 추가", "길찾기", "route" 요청에 사용. |
| triggers | ["이동경로","경로 추가","길찾기","route"] |
출발지/도착지 경로를 검색하고, 소요시간을 역산하여 Google Calendar에 이동 일정을 자동 추가하는 스킬.
이 스킬이 트리거되면, 가장 먼저 환경변수가 설정되어 있는지 확인한다.
source ~/.zshrc && echo "NCP_CLIENT_ID: ${NCP_CLIENT_ID:-미설정}" && echo "NCP_CLIENT_SECRET: $(if [ -n "$NCP_CLIENT_SECRET" ]; then echo 설정됨; else echo 미설정; fi)" && echo "ODSAY_API_KEY: $(if [ -n "$ODSAY_API_KEY" ]; then echo 설정됨; else echo 미설정; fi)" && echo "NAVER_CLIENT_ID: ${NAVER_CLIENT_ID:-미설정}" && echo "NAVER_CLIENT_SECRET: $(if [ -n "$NAVER_CLIENT_SECRET" ]; then echo 설정됨; else echo 미설정; fi)"
NCP 키 3개는 필수, NAVER 키 2개는 장소명 폴백용(선택)이다. NCP 키가 하나라도 미설정이면 실행을 중단하고 안내한다. NAVER 키가 미설정이면 경고만 출력하고 계속 진행한다 (장소명 검색 불가 안내).
[필수] 환경변수가 설정되지 않았습니다. ~/.zshrc에 다음을 추가해주세요:
export NCP_CLIENT_ID="네이버 클라우드 Client ID"
export NCP_CLIENT_SECRET="네이버 클라우드 Client Secret"
export ODSAY_API_KEY="ODsay Server API Key"
[선택 - 장소명 검색 폴백용]
export NAVER_CLIENT_ID="네이버 개발자센터 Client ID"
export NAVER_CLIENT_SECRET="네이버 개발자센터 Client Secret"
설정 후 터미널을 재시작하거나 source ~/.zshrc를 실행해주세요.
defaults:
home_address: "서울시 OO구 OO로 N"
home_coords: "127.0000000,37.0000000"
buffer_minutes: 10
transport_rule:
서울: "transit" # 🚇 대중교통 (ODsay)
서울외: "driving" # 🚗 자동차 (네이버 Directions 5)
driving_option: "trafast" # 가장 빠른 경로
장소 별칭:
aliases:
집: "서울시 OO구 OO로 N"
주소를 좌표(위경도)로 변환한다. Bash 도구에서 curl로 호출한다.
집 주소는 사전 캐싱된 좌표(127.0000000,37.0000000)를 사용하여 API 호출을 절약한다.
"집" 등 별칭은 aliases에서 매핑하여 주소로 변환 후 처리한다.
source ~/.zshrc && curl -s "https://maps.apigw.ntruss.com/map-geocode/v2/geocode?query=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("{주소}"))')" \
-H "x-ncp-apigw-api-key-id: $NCP_CLIENT_ID" \
-H "x-ncp-apigw-api-key: $NCP_CLIENT_SECRET" | python3 -c "
import sys,json
d=json.load(sys.stdin)
if d['meta']['totalCount'] == 0:
print('ERROR:NOT_FOUND')
elif d['meta']['totalCount'] > 1:
print('MULTIPLE')
for a in d['addresses']:
print(f'{a[\"roadAddress\"]}|{a[\"x\"]},{a[\"y\"]}')
else:
a=d['addresses'][0]
print(f'{a[\"roadAddress\"]}|{a[\"x\"]},{a[\"y\"]}')
"
응답 추출:
addresses[0].x → 경도 (longitude)addresses[0].y → 위도 (latitude)addresses[0].roadAddress → 도로명 주소 (서울 여부 판단에 사용)addresses[0].jibunAddress → 지번 주소 (roadAddress가 비어있을 때 폴백)주소 추출 우선순위: roadAddress → jibunAddress (둘 중 비어있지 않은 것 사용)
Geocoding 에러 처리:
Geocoding 결과가 0건일 때 (장소명, 상호명, 건물명 등 주소가 아닌 입력), 네이버 로컬 검색 API로 장소를 찾아 좌표를 얻는다.
이 API는
NAVER_CLIENT_ID/NAVER_CLIENT_SECRET(네이버 개발자센터)을 사용한다. route-planner의NCP_CLIENT_ID(네이버 클라우드)와 다른 키임에 주의.
source ~/.zshrc && curl -s "https://openapi.naver.com/v1/search/local.json?query=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("{장소명}"))')&display=5&sort=comment" \
-H "X-Naver-Client-Id: $NAVER_CLIENT_ID" \
-H "X-Naver-Client-Secret: $NAVER_CLIENT_SECRET" | python3 -c "
import sys,json,re
d=json.load(sys.stdin)
if 'items' in d and len(d['items']) > 0:
for i in d['items']:
name = re.sub('<[^>]+>','',i['title'])
# mapx, mapy는 카텍(KATEC) 좌표 → WGS84 변환 필요
print(f'{name}|{i[\"roadAddress\"]}|{i[\"mapx\"]}|{i[\"mapy\"]}')
else:
print('ERROR:NOT_FOUND')
"
좌표 변환 (KATEC → WGS84):
네이버 로컬 검색 API의 mapx, mapy는 카텍(KATEC) 좌표계이다.
Geocoding/Directions API에서 사용하는 WGS84(경위도)로 변환해야 한다.
로컬 검색에서 roadAddress를 얻은 후, 그 주소로 다시 1단계 Geocoding을 호출하여
정확한 WGS84 좌표를 얻는 방식으로 처리한다. (좌표 변환 라이브러리 불필요)
폴백 흐름 요약:
roadAddress로 다시 Geocoding → WGS84 좌표 획득로컬 검색도 결과 0건이면 → 사용자에게 정확한 주소 입력 요청
Geocoding 응답의 주소 필드에서 판단한다.
roadAddress가 비어있으면 jibunAddress를 사용한다.
출발지 주소에 "서울" 포함 AND 도착지 주소에 "서울" 포함
→ 이동수단: 대중교통 (ODsay)
그 외
→ 이동수단: 자동차 (네이버 Directions 5)
사용자가 이동수단을 명시적으로 지정하면 이 규칙을 오버라이드한다. 예: "자동차로 이동경로 추가해줘" → 서울 내라도 자동차 경로 사용
서울 외 지역이 포함된 경우 또는 사용자가 자동차를 지정한 경우 호출한다.
source ~/.zshrc && curl -s "https://maps.apigw.ntruss.com/map-direction/v1/driving?start={출발지lng},{출발지lat}&goal={도착지lng},{도착지lat}&option=trafast" \
-H "x-ncp-apigw-api-key-id: $NCP_CLIENT_ID" \
-H "x-ncp-apigw-api-key: $NCP_CLIENT_SECRET" | python3 -c "
import sys,json
d=json.load(sys.stdin)
if 'route' not in d:
print('ERROR:NO_ROUTE')
else:
s=d['route']['trafast'][0]['summary']
dist_km=s['distance']/1000
duration_min=s['duration']//60000
print(f'DRIVING|{duration_min}|{dist_km:.1f}')
"
추출 결과:
duration_min → 소요시간 (분)dist_km → 거리 (km)경로 실패 시: 사용자에게 "경로를 찾을 수 없습니다" 알림
출발지·도착지 모두 서울인 경우 기본 호출한다.
source ~/.zshrc && curl -s -G "https://api.odsay.com/v1/api/searchPubTransPathT" \
--data-urlencode "SX={출발지lng}" \
--data-urlencode "SY={출발지lat}" \
--data-urlencode "EX={도착지lng}" \
--data-urlencode "EY={도착지lat}" \
--data-urlencode "apiKey=$ODSAY_API_KEY" | python3 -c "
import sys,json
d=json.load(sys.stdin)
if 'result' not in d:
print('ERROR:NO_ROUTE')
else:
path=d['result']['path'][0]
info=path['info']
total_min=info['totalTime']
dist_km=info.get('totalDistance',0)/1000
bus_cnt=info['busTransitCount']
sub_cnt=info['subwayTransitCount']
# 구간별 경로 요약 (한줄)
segments=[]
for sp in path['subPath']:
t=sp['trafficType']
if t==1:
lanes=sp.get('lane',[{}])
name=lanes[0].get('name','') if lanes else ''
segments.append(f'{sp.get(\"startName\",\"\")}→{sp.get(\"endName\",\"\")}({name})')
elif t==2:
lanes=sp.get('lane',[])
bus_no=lanes[0].get('busNo','') if lanes else ''
segments.append(f'{bus_no}번버스 {sp.get(\"startName\",\"\")}→{sp.get(\"endName\",\"\")}')
route_summary=' → '.join(segments)
# 구간별 상세 경로 (캘린더 description용)
step=0
details=[]
for sp in path['subPath']:
t=sp['trafficType']
step+=1
sec_min=sp.get('sectionTime',0)
dist=sp.get('distance',0)
if t==3:
details.append(f'{step}. 🚶 도보 {sec_min}분 ({dist}m)')
elif t==1:
lanes=sp.get('lane',[{}])
name=lanes[0].get('name','') if lanes else ''
sn=sp.get('startName','')
en=sp.get('endName','')
cnt=sp.get('stationCount','')
details.append(f'{step}. 🚇 {name} ({sec_min}분, {cnt}정거장)')
details.append(f' 승차: {sn} → 하차: {en}')
elif t==2:
lanes=sp.get('lane',[])
bus_no=lanes[0].get('busNo','') if lanes else ''
sn=sp.get('startName','')
en=sp.get('endName','')
cnt=sp.get('stationCount','')
details.append(f'{step}. 🚌 {bus_no}번 버스 ({sec_min}분, {cnt}정거장)')
details.append(f' 승차: {sn} → 하차: {en}')
route_detail='\\n'.join(details)
print(f'TRANSIT|{total_min}|{dist_km:.1f}|버스{bus_cnt}+지하철{sub_cnt}|{route_summary}')
print(f'DETAIL|{route_detail}')
"
추출 결과:
total_min → 총 소요시간 (분)dist_km → 총 거리 (km)bus_cnt, sub_cnt → 환승 횟수route_summary → 구간별 경로 한줄 요약 (역명, 노선)route_detail → 구간별 상세 경로 (도보 포함, 캘린더 description에 사용)대중교통 경로 실패 시: "대중교통 경로를 찾을 수 없습니다. 자동차 경로로 대체할까요?" 제안
도착시간(미팅 시작시간)에서 역산하여 출발시간을 계산한다.
출발시간 = 미팅시작시간 - 소요시간 - 버퍼(10분)
종료시간 = 미팅시작시간
이동 일정의 종료시간을 미팅 시작시간과 동일하게 설정하여 캘린더에서 이동→미팅이 빈틈 없이 연결되도록 한다.
예시: 미팅 08:00, 소요시간 46분, 버퍼 10분 → 출발시간: 07:04 → 이동 일정: 07:04 ~ 08:00
소요시간 표시 규칙:
{N}분 (예: 46분){시간}시간{분}분 (예: 1시간20분)이동 일정을 생성하기 전에 primary 캘린더에서 해당 시간대에 기존 일정이 있는지 확인한다.
mcp__claude_ai_Google_Calendar__gcal_list_events(
calendarId="primary",
timeMin="{출발시간 RFC3339}",
timeMax="{종료시간 RFC3339}",
timeZone="Asia/Seoul"
)
mcp__claude_ai_Google_Calendar__gcal_create_event(
calendarId="primary",
summary="{이모지} 이동: {출발지} → {도착지} ({소요시간})",
description="📍 {출발지} → {도착지}\n{이모지} {이동수단} | 총 {소요시간} | {거리}km\n⏰ 여유시간 10분 포함\n\n🗺️ <a href=\"{naver_directions_url}\">네이버지도로 경로 보기</a>\n\n{route_detail}",
start="{출발시간 RFC3339}",
end="{종료시간 RFC3339}",
timeZone="Asia/Seoul",
colorId="8"
)
네이버지도 경로 URL (naver_directions_url):
대중교통: https://map.naver.com/v5/directions/{출발lng},{출발lat},{출발지명(URL인코딩)}/{도착lng},{도착lat},{도착지명(URL인코딩)}/-/transit
자동차: https://map.naver.com/v5/directions/{출발lng},{출발lat},{출발지명(URL인코딩)}/{도착lng},{도착lat},{도착지명(URL인코딩)}/-/car
Geocoding에서 얻은 좌표를 그대로 사용하며, 장소명은 URL 인코딩한다.
Google Calendar description은 HTML <a> 태그를 지원하므로 클릭 가능한 링크가 된다.
이모지 규칙:
제목 예시:
🚇 이동: 집 → 농심 본사 (46분)🚗 이동: 농심 본사 → 화성 롤링힐스 (1시간5분)"내일 일정 이동경로 넣어줘" 와 같이 요청하면:
일정 조회: 해당 날짜의 캘린더 일정을 조회한다
mcp__claude_ai_Google_Calendar__gcal_list_events(
calendarId="primary",
timeMin="{해당 날짜}T00:00:00",
timeMax="{해당 날짜}T23:59:59",
timeZone="Asia/Seoul"
)
필터링: location(장소)이 있는 일정만 추출한다. 이미 "이동:" 이 포함된 이벤트는 제외한다 (기존 이동 일정 중복 방지).
각 일정에 대해 시간순으로: a. 출발지 결정:
결과 리포트 출력
"집에서 농심 본사까지 내일 8시 도착" 과 같이 요청하면:
사용자 요청에서 파싱:
캘린더 컨텍스트 확인 (중요):
해당 날짜의 캘린더 일정을 조회하여, 출발지/도착지에 해당하는
일정의 location 필드가 있으면 그 정확한 주소를 Geocoding에 사용한다.
출발지/도착지 Geocoding → 좌표 변환
서울 여부 판단 → 이동수단 자동 선택 (사용자 지정 시 오버라이드)
해당 API로 경로 검색
시간 역산 → 캘린더 이벤트 생성
결과 리포트 출력
모든 이동 일정 생성이 완료되면 결과를 보고한다.
🗺️ 이동경로 추가 완료!
📅 {날짜}
{이모지} 이동: {출발지} → {도착지} ({소요시간})
{출발시간} ~ {종료시간} | {경로 요약}
✅ 캘린더에 추가됨
총 {N}건 추가, 충돌 {M}건
충돌이 있었던 경우:
⚠️ {시간} 충돌: 기존 일정 "{일정명}"과 겹침
→ 사용자 확인 후 추가됨 / 건너뜀
연쇄 이동 불가:
❌ {시간} 물리적 불가: {직전 일정}이 {종료시간}에 끝나지만
{다음 장소}까지 이동에 {소요시간}이 필요합니다
User의 일일 컨텍스트 싱크. Slack(외부 워크스페이스), Gmail, Google Calendar에서 최근 정보를 수집하고, 마에스트로 데이터(my-maestro 결과 JSON + Webex)도 함께 통합해서 하나의 문서로 정리한다. "싱크", "sync", "정보 수집" 요청에 사용.
AI·SW마에스트로(서울/부산) 멘토 활동 통합 관리. MY 멘토링·보고서 현황, 회의실 예약내역, 빈 회의실 조회, Webex 메시지 수집을 담당. User 멘토 전용. "마에스트로", "MY 멘토링", "내 예약", "빈 회의실", "회의실 비어있", "보고서 현황", "마에스트로 공지" 요청에 사용.
딴지게시판 특정 작성자(EXAMPLE_USER_ID) 사용자의 미국·한국 증시 마감 요약 글을 자동 수집해 Slack
세션 종료 시 작업 정리, 문서 업데이트, 학습 기록을 하는 스킬. "/wrap", "세션 정리", "마무리" 요청에 사용.
Playwright로 외부 Slack 워크스페이스의 채널 메시지를 수집한다. 게스트 권한으로 API 접근이 불가능한 워크스페이스 대상. "슬랙 스크래핑", "외부 슬랙 싱크", "슬랙 수집" 요청에 사용.
YouTube URL을 받으면 자막을 추출하고, Web Search로 자동자막 오류를 보정한 뒤, 요약-인사이트-전체 번역을 제공하는 스킬. "유튜브 번역", "영상 정리", "YouTube 요약" 요청에 사용.