@nomadamas/k-skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/k-skill.js +60 -0
- package/package.json +37 -0
- package/skills/assembly-bill-vote-search/instruction.md +111 -0
- package/skills/assembly-bill-vote-search/skill.json +10 -0
- package/skills/biz-health-check/instruction.md +69 -0
- package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
- package/skills/biz-health-check/skill.json +10 -0
- package/skills/bok-ecos-stats/instruction.md +95 -0
- package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
- package/skills/bok-ecos-stats/skill.json +10 -0
- package/skills/building-register-search/instruction.md +69 -0
- package/skills/building-register-search/scripts/building_register.py +309 -0
- package/skills/building-register-search/scripts/building_register_xml.py +32 -0
- package/skills/building-register-search/skill.json +9 -0
- package/skills/bunjang-search/instruction.md +154 -0
- package/skills/bunjang-search/skill.json +10 -0
- package/skills/catchtable-sniper/instruction.md +270 -0
- package/skills/catchtable-sniper/skill.json +9 -0
- package/skills/cheap-gas-nearby/instruction.md +107 -0
- package/skills/cheap-gas-nearby/skill.json +10 -0
- package/skills/corporate-registration-consulting/instruction.md +117 -0
- package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
- package/skills/corporate-registration-consulting/skill.json +8 -0
- package/skills/coupang-product-search/instruction.md +219 -0
- package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
- package/skills/coupang-product-search/skill.json +10 -0
- package/skills/court-auction-notice-search/instruction.md +201 -0
- package/skills/court-auction-notice-search/skill.json +9 -0
- package/skills/court-payment-order-assistant/instruction.md +120 -0
- package/skills/court-payment-order-assistant/skill.json +10 -0
- package/skills/d2b-notice-search/instruction.md +117 -0
- package/skills/d2b-notice-search/skill.json +10 -0
- package/skills/daangn-cars-search/instruction.md +91 -0
- package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
- package/skills/daangn-cars-search/skill.json +9 -0
- package/skills/daangn-jobs-search/instruction.md +90 -0
- package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
- package/skills/daangn-jobs-search/skill.json +9 -0
- package/skills/daangn-realty-search/instruction.md +114 -0
- package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
- package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
- package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
- package/skills/daangn-realty-search/skill.json +8 -0
- package/skills/daangn-used-goods-search/instruction.md +90 -0
- package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
- package/skills/daangn-used-goods-search/skill.json +9 -0
- package/skills/daishin-report-search/instruction.md +138 -0
- package/skills/daishin-report-search/skill.json +10 -0
- package/skills/daiso-product-search/instruction.md +172 -0
- package/skills/daiso-product-search/skill.json +8 -0
- package/skills/danawa-price-search/instruction.md +183 -0
- package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
- package/skills/danawa-price-search/skill.json +8 -0
- package/skills/delivery-tracking/instruction.md +358 -0
- package/skills/delivery-tracking/skill.json +8 -0
- package/skills/donation-place-search/instruction.md +129 -0
- package/skills/donation-place-search/skill.json +9 -0
- package/skills/emergency-room-beds/instruction.md +82 -0
- package/skills/emergency-room-beds/skill.json +8 -0
- package/skills/ev-charger-nearby/instruction.md +80 -0
- package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
- package/skills/ev-charger-nearby/skill.json +10 -0
- package/skills/ev-subsidy-status/instruction.md +165 -0
- package/skills/ev-subsidy-status/skill.json +10 -0
- package/skills/express-bus-booking/instruction.md +207 -0
- package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
- package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
- package/skills/express-bus-booking/skill.json +8 -0
- package/skills/fine-dust-location/instruction.md +89 -0
- package/skills/fine-dust-location/skill.json +9 -0
- package/skills/flight-ticket-search/instruction.md +237 -0
- package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
- package/skills/flight-ticket-search/skill.json +9 -0
- package/skills/foresttrip-vacancy/instruction.md +167 -0
- package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
- package/skills/foresttrip-vacancy/skill.json +10 -0
- package/skills/fsc-corporate-info/instruction.md +57 -0
- package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
- package/skills/fsc-corporate-info/skill.json +10 -0
- package/skills/g2b-order-plan-search/instruction.md +131 -0
- package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
- package/skills/g2b-order-plan-search/skill.json +10 -0
- package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
- package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
- package/skills/g2b-sanctioned-supplier/skill.json +10 -0
- package/skills/gangnamunni-clinic-search/instruction.md +113 -0
- package/skills/gangnamunni-clinic-search/skill.json +9 -0
- package/skills/geeknews-search/instruction.md +69 -0
- package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
- package/skills/geeknews-search/skill.json +8 -0
- package/skills/gongsijiga-search/instruction.md +128 -0
- package/skills/gongsijiga-search/skill.json +9 -0
- package/skills/gov-overseas-trip-report/instruction.md +488 -0
- package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
- package/skills/gov-overseas-trip-report/skill.json +9 -0
- package/skills/han-river-water-level/instruction.md +83 -0
- package/skills/han-river-water-level/skill.json +10 -0
- package/skills/highway-traffic-status/instruction.md +88 -0
- package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
- package/skills/highway-traffic-status/skill.json +11 -0
- package/skills/hipass-receipt/instruction.md +97 -0
- package/skills/hipass-receipt/skill.json +10 -0
- package/skills/hola-poke-yeoksam/instruction.md +247 -0
- package/skills/hola-poke-yeoksam/skill.json +8 -0
- package/skills/household-waste-info/instruction.md +117 -0
- package/skills/household-waste-info/skill.json +10 -0
- package/skills/housing-official-price/instruction.md +177 -0
- package/skills/housing-official-price/skill.json +10 -0
- package/skills/hwp/instruction.md +206 -0
- package/skills/hwp/skill.json +8 -0
- package/skills/intercity-bus-booking/instruction.md +189 -0
- package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
- package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
- package/skills/intercity-bus-booking/skill.json +8 -0
- package/skills/iros-registry-automation/instruction.md +229 -0
- package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
- package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
- package/skills/iros-registry-automation/skill.json +9 -0
- package/skills/job-posting-match/instruction.md +130 -0
- package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
- package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
- package/skills/job-posting-match/skill.json +8 -0
- package/skills/jobkorea-talent-search/instruction.md +118 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
- package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
- package/skills/jobkorea-talent-search/skill.json +9 -0
- package/skills/joseon-sillok-search/instruction.md +76 -0
- package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
- package/skills/joseon-sillok-search/skill.json +8 -0
- package/skills/k-dart/instruction.md +406 -0
- package/skills/k-dart/skill.json +8 -0
- package/skills/k-schoollunch-menu/instruction.md +109 -0
- package/skills/k-schoollunch-menu/skill.json +9 -0
- package/skills/k-skill-cleaner/instruction.md +80 -0
- package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
- package/skills/k-skill-cleaner/skill.json +8 -0
- package/skills/k-skill-setup/instruction.md +253 -0
- package/skills/k-skill-setup/skill.json +11 -0
- package/skills/kakao-bar-nearby/instruction.md +76 -0
- package/skills/kakao-bar-nearby/skill.json +8 -0
- package/skills/kakao-map/instruction.md +176 -0
- package/skills/kakao-map/skill.json +10 -0
- package/skills/kakaotalk-mac/instruction.md +189 -0
- package/skills/kakaotalk-mac/skill.json +8 -0
- package/skills/kbl-results/instruction.md +89 -0
- package/skills/kbl-results/skill.json +9 -0
- package/skills/kbo-results/instruction.md +82 -0
- package/skills/kbo-results/skill.json +8 -0
- package/skills/keris-academic-search/instruction.md +81 -0
- package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
- package/skills/keris-academic-search/skill.json +10 -0
- package/skills/kleague-results/instruction.md +92 -0
- package/skills/kleague-results/skill.json +8 -0
- package/skills/kopis-performance-search/instruction.md +109 -0
- package/skills/kopis-performance-search/skill.json +10 -0
- package/skills/korea-weather/instruction.md +93 -0
- package/skills/korea-weather/skill.json +10 -0
- package/skills/korean-character-count/instruction.md +87 -0
- package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
- package/skills/korean-character-count/skill.json +8 -0
- package/skills/korean-cinema-search/instruction.md +177 -0
- package/skills/korean-cinema-search/skill.json +8 -0
- package/skills/korean-heritage-search/instruction.md +106 -0
- package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
- package/skills/korean-heritage-search/skill.json +8 -0
- package/skills/korean-holiday-calendar/instruction.md +97 -0
- package/skills/korean-holiday-calendar/skill.json +9 -0
- package/skills/korean-humanizer/instruction.md +389 -0
- package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
- package/skills/korean-humanizer/skill.json +8 -0
- package/skills/korean-jangbu-for/instruction.md +133 -0
- package/skills/korean-jangbu-for/scripts/install.sh +237 -0
- package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
- package/skills/korean-jangbu-for/skill.json +8 -0
- package/skills/korean-law-search/instruction.md +126 -0
- package/skills/korean-law-search/skill.json +10 -0
- package/skills/korean-marathon-schedule/instruction.md +111 -0
- package/skills/korean-marathon-schedule/skill.json +9 -0
- package/skills/korean-middle-korean/instruction.md +79 -0
- package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
- package/skills/korean-middle-korean/skill.json +8 -0
- package/skills/korean-patent-search/instruction.md +79 -0
- package/skills/korean-patent-search/scripts/patent_search.py +409 -0
- package/skills/korean-patent-search/skill.json +9 -0
- package/skills/korean-privacy-terms/instruction.md +128 -0
- package/skills/korean-privacy-terms/scripts/install.sh +108 -0
- package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
- package/skills/korean-privacy-terms/skill.json +8 -0
- package/skills/korean-scholarship-search/instruction.md +317 -0
- package/skills/korean-scholarship-search/references/report-format.md +40 -0
- package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
- package/skills/korean-scholarship-search/references/search-clues.md +58 -0
- package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
- package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
- package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
- package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
- package/skills/korean-scholarship-search/skill.json +8 -0
- package/skills/korean-slang-writing/instruction.md +181 -0
- package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
- package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
- package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
- package/skills/korean-slang-writing/skill.json +8 -0
- package/skills/korean-spell-check/instruction.md +105 -0
- package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
- package/skills/korean-spell-check/skill.json +9 -0
- package/skills/korean-stock-search/instruction.md +194 -0
- package/skills/korean-stock-search/skill.json +10 -0
- package/skills/korean-transit-route/instruction.md +113 -0
- package/skills/korean-transit-route/skill.json +10 -0
- package/skills/kosis-stats/instruction.md +232 -0
- package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
- package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
- package/skills/kosis-stats/skill.json +9 -0
- package/skills/kr-whois-lookup/instruction.md +107 -0
- package/skills/kr-whois-lookup/skill.json +10 -0
- package/skills/kstartup-search/instruction.md +186 -0
- package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
- package/skills/kstartup-search/skill.json +11 -0
- package/skills/ktx-booking/instruction.md +244 -0
- package/skills/ktx-booking/skill.json +10 -0
- package/skills/lck-analytics/instruction.md +192 -0
- package/skills/lck-analytics/scripts/_lib.js +103 -0
- package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
- package/skills/lck-analytics/scripts/build-match-report.js +44 -0
- package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
- package/skills/lck-analytics/skill.json +9 -0
- package/skills/lh-notice-search/instruction.md +206 -0
- package/skills/lh-notice-search/skill.json +10 -0
- package/skills/library-book-search/instruction.md +139 -0
- package/skills/library-book-search/skill.json +9 -0
- package/skills/local-election-candidate-search/instruction.md +77 -0
- package/skills/local-election-candidate-search/skill.json +8 -0
- package/skills/localdata-business-status/instruction.md +64 -0
- package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
- package/skills/localdata-business-status/skill.json +8 -0
- package/skills/lotto-results/instruction.md +80 -0
- package/skills/lotto-results/skill.json +8 -0
- package/skills/lovebug-report/instruction.md +185 -0
- package/skills/lovebug-report/skill.json +9 -0
- package/skills/market-kurly-search/instruction.md +125 -0
- package/skills/market-kurly-search/skill.json +8 -0
- package/skills/mfds-drug-safety/instruction.md +86 -0
- package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
- package/skills/mfds-drug-safety/skill.json +10 -0
- package/skills/mfds-food-safety/instruction.md +126 -0
- package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
- package/skills/mfds-food-safety/skill.json +10 -0
- package/skills/myrealtrip-search/instruction.md +239 -0
- package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
- package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
- package/skills/myrealtrip-search/skill.json +9 -0
- package/skills/naming-house/instruction.md +146 -0
- package/skills/naming-house/skill.json +8 -0
- package/skills/national-pension-workplace/instruction.md +64 -0
- package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
- package/skills/national-pension-workplace/skill.json +10 -0
- package/skills/naver-ad-performance/instruction.md +108 -0
- package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
- package/skills/naver-ad-performance/skill.json +9 -0
- package/skills/naver-blog-research/instruction.md +128 -0
- package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
- package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
- package/skills/naver-blog-research/scripts/naver_read.py +256 -0
- package/skills/naver-blog-research/scripts/naver_search.py +192 -0
- package/skills/naver-blog-research/skill.json +8 -0
- package/skills/naver-news-search/instruction.md +103 -0
- package/skills/naver-news-search/skill.json +10 -0
- package/skills/naver-shopping-search/instruction.md +94 -0
- package/skills/naver-shopping-search/skill.json +11 -0
- package/skills/nhis-care-checkup-search/instruction.md +116 -0
- package/skills/nhis-care-checkup-search/skill.json +10 -0
- package/skills/nts-business-registration/instruction.md +115 -0
- package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
- package/skills/nts-business-registration/skill.json +10 -0
- package/skills/nts-tax-delinquency/instruction.md +54 -0
- package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
- package/skills/nts-tax-delinquency/skill.json +8 -0
- package/skills/ohou-today-deal/instruction.md +182 -0
- package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
- package/skills/ohou-today-deal/skill.json +9 -0
- package/skills/olive-young-search/instruction.md +154 -0
- package/skills/olive-young-search/skill.json +8 -0
- package/skills/parking-lot-search/instruction.md +96 -0
- package/skills/parking-lot-search/skill.json +9 -0
- package/skills/popbill/instruction.md +146 -0
- package/skills/popbill/scripts/popbill_cli.py +284 -0
- package/skills/popbill/scripts/popbill_registry.py +54 -0
- package/skills/popbill/scripts/popbill_safety.py +37 -0
- package/skills/popbill/scripts/popbill_templates.py +71 -0
- package/skills/popbill/skill.json +9 -0
- package/skills/public-restroom-nearby/instruction.md +89 -0
- package/skills/public-restroom-nearby/skill.json +9 -0
- package/skills/real-estate-search/instruction.md +172 -0
- package/skills/real-estate-search/skill.json +10 -0
- package/skills/rhwp-advanced/instruction.md +145 -0
- package/skills/rhwp-advanced/skill.json +8 -0
- package/skills/rhwp-edit/instruction.md +153 -0
- package/skills/rhwp-edit/skill.json +8 -0
- package/skills/s2b-notice-search/instruction.md +66 -0
- package/skills/s2b-notice-search/skill.json +9 -0
- package/skills/saju-fortune/instruction.md +177 -0
- package/skills/saju-fortune/skill.json +8 -0
- package/skills/saramin-talent-search/instruction.md +119 -0
- package/skills/saramin-talent-search/skill.json +9 -0
- package/skills/seoul-bike/instruction.md +83 -0
- package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
- package/skills/seoul-bike/skill.json +10 -0
- package/skills/seoul-density/instruction.md +109 -0
- package/skills/seoul-density/scripts/seoul_density.py +271 -0
- package/skills/seoul-density/skill.json +10 -0
- package/skills/seoul-subway-arrival/instruction.md +85 -0
- package/skills/seoul-subway-arrival/skill.json +9 -0
- package/skills/sh-notice-search/instruction.md +150 -0
- package/skills/sh-notice-search/skill.json +9 -0
- package/skills/srt-booking/instruction.md +181 -0
- package/skills/srt-booking/scripts/srt_booking.py +272 -0
- package/skills/srt-booking/scripts/srt_seats.py +156 -0
- package/skills/srt-booking/skill.json +20 -0
- package/skills/subway-lost-property/instruction.md +93 -0
- package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
- package/skills/subway-lost-property/skill.json +8 -0
- package/skills/ticket-availability/instruction.md +175 -0
- package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
- package/skills/ticket-availability/skill.json +9 -0
- package/skills/toss-securities/instruction.md +116 -0
- package/skills/toss-securities/skill.json +9 -0
- package/skills/used-car-price-search/instruction.md +109 -0
- package/skills/used-car-price-search/skill.json +8 -0
- package/skills/yebigun-training/instruction.md +177 -0
- package/skills/yebigun-training/skill.json +9 -0
- package/skills/zipcode-search/instruction.md +159 -0
- package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
- package/skills/zipcode-search/skill.json +8 -0
- package/src/assemble.js +134 -0
- package/src/detect.js +18 -0
- package/templates/action-account.md +4 -0
- package/templates/action-booking.md +5 -0
- package/templates/action-commerce.md +5 -0
- package/templates/action-communication.md +5 -0
- package/templates/action-submission.md +5 -0
- package/templates/browser.md +4 -0
- package/templates/core.md +5 -0
- package/templates/hard-boundary.md +3 -0
- package/templates/local.md +2 -0
- package/templates/lookup.md +2 -0
- package/templates/proxy.md +2 -0
- package/templates/vault.md +4 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import html
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
import urllib.error
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import urllib.request
|
|
11
|
+
import xml.etree.ElementTree as ET
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Iterable
|
|
14
|
+
|
|
15
|
+
LIST_URL = "https://www.khs.go.kr/cha/SearchKindOpenapiList.do"
|
|
16
|
+
DETAIL_URL = "https://www.khs.go.kr/cha/SearchKindOpenapiDt.do"
|
|
17
|
+
EVENT_URL = "https://www.khs.go.kr/cha/openapi/selectEventListOpenapi.do"
|
|
18
|
+
OPEN_API_GUIDE_URL = "https://www.khs.go.kr/html/HtmlPage.do?mn=NS_04_04_03&pg=%2Fpublicinfo%2Fpbinfo3_0201.jsp"
|
|
19
|
+
USER_AGENT = "k-skill-korean-heritage-search/1.0"
|
|
20
|
+
|
|
21
|
+
REGION_CODES = {
|
|
22
|
+
"서울": "11",
|
|
23
|
+
"부산": "21",
|
|
24
|
+
"대구": "22",
|
|
25
|
+
"인천": "23",
|
|
26
|
+
"광주": "24",
|
|
27
|
+
"대전": "25",
|
|
28
|
+
"울산": "26",
|
|
29
|
+
"경기": "31",
|
|
30
|
+
"강원": "32",
|
|
31
|
+
"충북": "33",
|
|
32
|
+
"충남": "34",
|
|
33
|
+
"전북": "35",
|
|
34
|
+
"전남": "36",
|
|
35
|
+
"경북": "37",
|
|
36
|
+
"경남": "38",
|
|
37
|
+
"제주": "39",
|
|
38
|
+
"세종": "45",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class HeritageApiError(RuntimeError):
|
|
43
|
+
"""Raised when the official heritage API cannot return valid XML."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _clean_text(value: str | None) -> str:
|
|
47
|
+
if not value:
|
|
48
|
+
return ""
|
|
49
|
+
text = html.unescape(value)
|
|
50
|
+
text = re.sub(r"<br\s*/?>", "\n", text, flags=re.IGNORECASE)
|
|
51
|
+
text = re.sub(r"</?(?:p|div|li|ul|ol|h[1-6])[^>]*>", "\n", text, flags=re.IGNORECASE)
|
|
52
|
+
text = re.sub(r"<[^>]+>", "", text)
|
|
53
|
+
text = re.sub(r"[ \t\f\v]+", " ", text)
|
|
54
|
+
text = re.sub(r"\n[ \t]+", "\n", text)
|
|
55
|
+
return text.strip()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _child_text(parent: ET.Element, name: str) -> str:
|
|
59
|
+
child = parent.find(name)
|
|
60
|
+
return _clean_text(child.text if child is not None else "")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _int_or_none(value: str) -> int | None:
|
|
64
|
+
try:
|
|
65
|
+
return int(value)
|
|
66
|
+
except (TypeError, ValueError):
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _float_or_none(value: str) -> float | None:
|
|
71
|
+
try:
|
|
72
|
+
return float(value)
|
|
73
|
+
except (TypeError, ValueError):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _request_xml(url: str, params: dict[str, str], timeout: float = 20.0) -> ET.Element:
|
|
78
|
+
query = urllib.parse.urlencode({key: value for key, value in params.items() if value != ""})
|
|
79
|
+
request = urllib.request.Request(
|
|
80
|
+
f"{url}?{query}",
|
|
81
|
+
headers={"Accept": "application/xml", "User-Agent": USER_AGENT},
|
|
82
|
+
)
|
|
83
|
+
try:
|
|
84
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
85
|
+
body = response.read()
|
|
86
|
+
except urllib.error.HTTPError as exc:
|
|
87
|
+
raise HeritageApiError(f"official heritage API returned HTTP {exc.code}") from exc
|
|
88
|
+
except (urllib.error.URLError, TimeoutError) as exc:
|
|
89
|
+
raise HeritageApiError(f"official heritage API request failed: {exc.reason if isinstance(exc, urllib.error.URLError) else exc}") from exc
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
root = ET.fromstring(body)
|
|
93
|
+
except ET.ParseError as exc:
|
|
94
|
+
raise HeritageApiError("official heritage API returned invalid XML") from exc
|
|
95
|
+
if root.tag.rsplit("}", 1)[-1] != "result":
|
|
96
|
+
raise HeritageApiError("official heritage API returned an unexpected XML root")
|
|
97
|
+
return root
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _source_payload(url: str, fetched_at: str) -> dict[str, str]:
|
|
101
|
+
return {"url": url, "fetched_at": fetched_at}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _now_iso() -> str:
|
|
105
|
+
return datetime.now(timezone.utc).isoformat()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def normalize_region(region: str) -> str:
|
|
109
|
+
value = region.strip()
|
|
110
|
+
if value in REGION_CODES:
|
|
111
|
+
return REGION_CODES[value]
|
|
112
|
+
if value in REGION_CODES.values():
|
|
113
|
+
return value
|
|
114
|
+
raise ValueError(f"unknown region: {region}; use a Korean region name or code")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _list_item(item: ET.Element) -> dict[str, object]:
|
|
118
|
+
return {
|
|
119
|
+
"name": _child_text(item, "ccbaMnm1"),
|
|
120
|
+
"name_hanja": _child_text(item, "ccbaMnm2"),
|
|
121
|
+
"heritage_type": _child_text(item, "ccmaName"),
|
|
122
|
+
"province": _child_text(item, "ccbaCtcdNm"),
|
|
123
|
+
"district": _child_text(item, "ccsiName"),
|
|
124
|
+
"admin": _child_text(item, "ccbaAdmin"),
|
|
125
|
+
"latitude": _float_or_none(_child_text(item, "latitude")),
|
|
126
|
+
"longitude": _float_or_none(_child_text(item, "longitude")),
|
|
127
|
+
"designated_status": _child_text(item, "ccbaCncl"),
|
|
128
|
+
"updated_at": _child_text(item, "regDt"),
|
|
129
|
+
"heritage_code": {
|
|
130
|
+
"kind": _child_text(item, "ccbaKdcd"),
|
|
131
|
+
"number": _child_text(item, "ccbaAsno"),
|
|
132
|
+
"province": _child_text(item, "ccbaCtcd"),
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def parse_search_result(root: ET.Element, *, url: str, fetched_at: str) -> dict[str, object]:
|
|
138
|
+
items = [_list_item(item) for item in root.findall("./item")]
|
|
139
|
+
return {
|
|
140
|
+
"total_results": _int_or_none(_child_text(root, "totalCnt")) or 0,
|
|
141
|
+
"page": _int_or_none(_child_text(root, "pageIndex")) or 1,
|
|
142
|
+
"page_size": _int_or_none(_child_text(root, "pageUnit")) or len(items),
|
|
143
|
+
"items": items,
|
|
144
|
+
"source": _source_payload(url, fetched_at),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def search_heritage(query: str = "", region: str = "", page: int = 1, limit: int = 10) -> dict[str, object]:
|
|
149
|
+
if page < 1:
|
|
150
|
+
raise ValueError("page must be at least 1")
|
|
151
|
+
if not 1 <= limit <= 100:
|
|
152
|
+
raise ValueError("limit must be between 1 and 100")
|
|
153
|
+
params = {
|
|
154
|
+
"pageUnit": str(limit),
|
|
155
|
+
"pageIndex": str(page),
|
|
156
|
+
"ccbaCncl": "N",
|
|
157
|
+
"ccbaMnm1": query.strip(),
|
|
158
|
+
"ccbaCtcd": normalize_region(region) if region else "",
|
|
159
|
+
}
|
|
160
|
+
fetched_at = _now_iso()
|
|
161
|
+
root = _request_xml(LIST_URL, params)
|
|
162
|
+
return parse_search_result(root, url=LIST_URL, fetched_at=fetched_at)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def parse_detail_result(root: ET.Element, *, url: str, fetched_at: str) -> dict[str, object]:
|
|
166
|
+
item = root.find("./item")
|
|
167
|
+
if item is None:
|
|
168
|
+
raise HeritageApiError("heritage detail was not found")
|
|
169
|
+
if not _child_text(item, "ccbaMnm1") or any(
|
|
170
|
+
not _child_text(root, field) for field in ("ccbaKdcd", "ccbaAsno", "ccbaCtcd")
|
|
171
|
+
):
|
|
172
|
+
raise HeritageApiError("heritage detail was not found")
|
|
173
|
+
return {
|
|
174
|
+
"name": _child_text(item, "ccbaMnm1"),
|
|
175
|
+
"name_hanja": _child_text(item, "ccbaMnm2"),
|
|
176
|
+
"heritage_type": _child_text(item, "ccmaName"),
|
|
177
|
+
"classification": [
|
|
178
|
+
value for value in (
|
|
179
|
+
_child_text(item, "gcodeName"),
|
|
180
|
+
_child_text(item, "bcodeName"),
|
|
181
|
+
_child_text(item, "mcodeName"),
|
|
182
|
+
_child_text(item, "scodeName"),
|
|
183
|
+
) if value
|
|
184
|
+
],
|
|
185
|
+
"quantity": _child_text(item, "ccbaQuan"),
|
|
186
|
+
"designated_date": _child_text(item, "ccbaAsdt"),
|
|
187
|
+
"province": _child_text(item, "ccbaCtcdNm"),
|
|
188
|
+
"district": _child_text(item, "ccsiName"),
|
|
189
|
+
"address": _child_text(item, "ccbaLcad"),
|
|
190
|
+
"built_or_created": _child_text(item, "ccceName"),
|
|
191
|
+
"ownership": _child_text(item, "ccbaPoss"),
|
|
192
|
+
"admin": _child_text(item, "ccbaAdmin"),
|
|
193
|
+
"description": _child_text(item, "content"),
|
|
194
|
+
"image_url": _child_text(item, "imageUrl"),
|
|
195
|
+
"latitude": _float_or_none(_child_text(root, "latitude")),
|
|
196
|
+
"longitude": _float_or_none(_child_text(root, "longitude")),
|
|
197
|
+
"heritage_code": {
|
|
198
|
+
"kind": _child_text(root, "ccbaKdcd"),
|
|
199
|
+
"number": _child_text(root, "ccbaAsno"),
|
|
200
|
+
"province": _child_text(root, "ccbaCtcd"),
|
|
201
|
+
},
|
|
202
|
+
"source": _source_payload(url, fetched_at),
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_heritage_detail(kind: str, number: str, province_code: str) -> dict[str, object]:
|
|
207
|
+
values = {"ccbaKdcd": kind.strip(), "ccbaAsno": number.strip(), "ccbaCtcd": province_code.strip()}
|
|
208
|
+
if any(not value for value in values.values()):
|
|
209
|
+
raise ValueError("detail requires ccba-kdcd, ccba-asno, and ccba-ctcd")
|
|
210
|
+
fetched_at = _now_iso()
|
|
211
|
+
root = _request_xml(DETAIL_URL, values)
|
|
212
|
+
return parse_detail_result(root, url=DETAIL_URL, fetched_at=fetched_at)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _event_item(item: ET.Element) -> dict[str, object]:
|
|
216
|
+
return {
|
|
217
|
+
"title": _child_text(item, "subTitle"),
|
|
218
|
+
"description": _child_text(item, "subContent"),
|
|
219
|
+
"start_date": _child_text(item, "sDate"),
|
|
220
|
+
"end_date": _child_text(item, "eDate"),
|
|
221
|
+
"display_date": _child_text(item, "subDate"),
|
|
222
|
+
"province": _child_text(item, "sido"),
|
|
223
|
+
"district": _child_text(item, "gugun"),
|
|
224
|
+
"venue": _child_text(item, "subDesc"),
|
|
225
|
+
"organizer": _child_text(item, "groupName"),
|
|
226
|
+
"audience": _child_text(item, "subDesc_2"),
|
|
227
|
+
"fee": _child_text(item, "subDesc_3"),
|
|
228
|
+
"contact": _child_text(item, "contact"),
|
|
229
|
+
"url": _child_text(item, "subPath"),
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def parse_event_result(
|
|
234
|
+
root: ET.Element,
|
|
235
|
+
*,
|
|
236
|
+
year: int,
|
|
237
|
+
month: int,
|
|
238
|
+
region: str,
|
|
239
|
+
limit: int,
|
|
240
|
+
url: str,
|
|
241
|
+
fetched_at: str,
|
|
242
|
+
) -> dict[str, object]:
|
|
243
|
+
items: Iterable[dict[str, object]] = (_event_item(item) for item in root.findall("./item"))
|
|
244
|
+
if region:
|
|
245
|
+
needle = region.strip()
|
|
246
|
+
items = (
|
|
247
|
+
item for item in items
|
|
248
|
+
if needle in str(item["province"]) or needle in str(item["district"])
|
|
249
|
+
)
|
|
250
|
+
selected = list(items)[:limit]
|
|
251
|
+
return {
|
|
252
|
+
"year": year,
|
|
253
|
+
"month": month,
|
|
254
|
+
"returned_count": len(selected),
|
|
255
|
+
"items": selected,
|
|
256
|
+
"source": _source_payload(url, fetched_at),
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def search_events(year: int, month: int, region: str = "", limit: int = 20) -> dict[str, object]:
|
|
261
|
+
if not 1 <= year <= 9999:
|
|
262
|
+
raise ValueError("year must be between 1 and 9999")
|
|
263
|
+
if not 1 <= month <= 12:
|
|
264
|
+
raise ValueError("month must be between 1 and 12")
|
|
265
|
+
if not 1 <= limit <= 100:
|
|
266
|
+
raise ValueError("limit must be between 1 and 100")
|
|
267
|
+
params = {"searchYear": str(year), "searchMonth": str(month)}
|
|
268
|
+
fetched_at = _now_iso()
|
|
269
|
+
root = _request_xml(EVENT_URL, params)
|
|
270
|
+
return parse_event_result(
|
|
271
|
+
root,
|
|
272
|
+
year=year,
|
|
273
|
+
month=month,
|
|
274
|
+
region=region,
|
|
275
|
+
limit=limit,
|
|
276
|
+
url=EVENT_URL,
|
|
277
|
+
fetched_at=fetched_at,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
282
|
+
parser = argparse.ArgumentParser(description="Search official Korean heritage records and events.")
|
|
283
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
284
|
+
|
|
285
|
+
search = subparsers.add_parser("search", help="search heritage records")
|
|
286
|
+
search.add_argument("--query", default="", help="heritage name keyword")
|
|
287
|
+
search.add_argument("--region", default="", help="Korean province name or code")
|
|
288
|
+
search.add_argument("--page", type=int, default=1)
|
|
289
|
+
search.add_argument("--limit", type=int, default=10)
|
|
290
|
+
|
|
291
|
+
detail = subparsers.add_parser("detail", help="show one heritage record")
|
|
292
|
+
detail.add_argument("--ccba-kdcd", required=True)
|
|
293
|
+
detail.add_argument("--ccba-asno", required=True)
|
|
294
|
+
detail.add_argument("--ccba-ctcd", required=True)
|
|
295
|
+
|
|
296
|
+
events = subparsers.add_parser("events", help="search monthly heritage events")
|
|
297
|
+
events.add_argument("--year", type=int, required=True)
|
|
298
|
+
events.add_argument("--month", type=int, required=True)
|
|
299
|
+
events.add_argument("--region", default="", help="province or district text filter")
|
|
300
|
+
events.add_argument("--limit", type=int, default=20)
|
|
301
|
+
return parser
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def main(argv: list[str] | None = None) -> int:
|
|
305
|
+
args = build_parser().parse_args(argv)
|
|
306
|
+
try:
|
|
307
|
+
if args.command == "search":
|
|
308
|
+
payload = search_heritage(args.query, args.region, args.page, args.limit)
|
|
309
|
+
elif args.command == "detail":
|
|
310
|
+
payload = get_heritage_detail(args.ccba_kdcd, args.ccba_asno, args.ccba_ctcd)
|
|
311
|
+
else:
|
|
312
|
+
payload = search_events(args.year, args.month, args.region, args.limit)
|
|
313
|
+
except (HeritageApiError, ValueError) as exc:
|
|
314
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
315
|
+
return 2
|
|
316
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
317
|
+
return 0
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
if __name__ == "__main__":
|
|
321
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "korean-heritage-search",
|
|
3
|
+
"description": "Search official Korean national heritage records, details, locations, and monthly heritage events from the Korea Heritage Service Open API.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"lookup"
|
|
6
|
+
],
|
|
7
|
+
"frontmatter": "name: korean-heritage-search\ndescription: Search official Korean national heritage records, details, locations, and monthly heritage events from the Korea Heritage Service Open API.\nlicense: MIT\nmetadata:\n category: history\n locale: ko-KR\n phase: v1"
|
|
8
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Korean Holiday Calendar
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
공공데이터포털 **한국천문연구원_특일 정보**(data.go.kr `15012690`)의 `SpcdeInfoService`를 `k-skill-proxy` 경유로 호출한다.
|
|
6
|
+
|
|
7
|
+
지원 operation:
|
|
8
|
+
|
|
9
|
+
- `rest` → `getRestDeInfo` 공휴일
|
|
10
|
+
- `national` → `getHoliDeInfo` 국경일
|
|
11
|
+
- `anniversary` → `getAnniversaryInfo` 기념일
|
|
12
|
+
- `solarTerm` → `get24DivisionsInfo` 24절기
|
|
13
|
+
- `sundry` → `getSundryDayInfo` 잡절
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- "2026년 8월 공휴일 알려줘"
|
|
18
|
+
- "오늘이 공휴일인지 확인해줘"
|
|
19
|
+
- "2026년 24절기 조회해줘"
|
|
20
|
+
- "대체공휴일 있는지 확인해줘"
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- 인터넷 연결
|
|
25
|
+
- hosted/self-host `k-skill-proxy`의 `/v1/korean-holiday/calendar` route 접근 가능
|
|
26
|
+
|
|
27
|
+
## Credential requirements
|
|
28
|
+
|
|
29
|
+
- 사용자 측 필수 시크릿 없음.
|
|
30
|
+
- `KSKILL_PROXY_BASE_URL` — self-host·별도 프록시를 쓸 때만 설정. 비우면 기본 hosted `https://k-skill-proxy.nomadamas.org` 를 사용한다.
|
|
31
|
+
- `DATA_GO_KR_API_KEY` 는 프록시 운영 서버 환경에만 둔다. 공공데이터포털 `한국천문연구원_특일 정보`(15012690) 활용신청이 승인돼 있어야 한다.
|
|
32
|
+
|
|
33
|
+
키 발급:
|
|
34
|
+
|
|
35
|
+
- API 페이지: <https://www.data.go.kr/data/15012690/openapi.do>
|
|
36
|
+
- 공공데이터포털 이용 가이드: <https://www.data.go.kr/ugs/selectPublicDataUseGuideView.do>
|
|
37
|
+
|
|
38
|
+
## Inputs
|
|
39
|
+
|
|
40
|
+
- `operation`/`type`: `rest`(기본), `national`, `anniversary`, `solarTerm`, `sundry`
|
|
41
|
+
- `year`/`solYear`: 4자리 연도
|
|
42
|
+
- `month`/`solMonth`: 선택, `01`-`12`
|
|
43
|
+
- `page`/`pageNo`: 기본 1
|
|
44
|
+
- `limit`/`numOfRows`: 기본 100, 최대 1000
|
|
45
|
+
|
|
46
|
+
## Workflow
|
|
47
|
+
|
|
48
|
+
### 1. Query holidays for the target month or year
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
BASE="${KSKILL_PROXY_BASE_URL:-https://k-skill-proxy.nomadamas.org}"
|
|
52
|
+
curl -fsS --get "$BASE/v1/korean-holiday/calendar" \
|
|
53
|
+
--data-urlencode "operation=rest" \
|
|
54
|
+
--data-urlencode "year=2026" \
|
|
55
|
+
--data-urlencode "month=08"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Decide holiday status from upstream fields
|
|
59
|
+
|
|
60
|
+
응답 item의 핵심 필드:
|
|
61
|
+
|
|
62
|
+
- `locdate`: `YYYYMMDD`
|
|
63
|
+
- `dateName`: 특일 이름
|
|
64
|
+
- `isHoliday`: `Y`면 공휴일, `N`이면 특일이지만 휴일 아님
|
|
65
|
+
- `dateKind`, `seq`: upstream 분류/순번
|
|
66
|
+
|
|
67
|
+
"오늘/내일이 공휴일인지" 묻는 경우 KST 기준 날짜를 `YYYYMMDD`로 만든 뒤 해당 `locdate`의 `isHoliday`를 확인한다.
|
|
68
|
+
|
|
69
|
+
## Failure modes
|
|
70
|
+
|
|
71
|
+
- `400 bad_request`: 연도/월/operation/page 값이 잘못됨.
|
|
72
|
+
- `503 upstream_not_configured`: 프록시 서버에 `DATA_GO_KR_API_KEY` 없음 또는 15012690 활용신청 미승인.
|
|
73
|
+
- `502 upstream_forbidden`: data.go.kr gateway가 키를 거부함.
|
|
74
|
+
- 빈 결과: 해당 연월/operation에 특일 없음. operation을 바꿔 재조회한다.
|
|
75
|
+
- 24절기/잡절 operation은 한국천문연구원 특일 서비스의 관례적 operation 이름을 사용한다. live smoke에서 upstream 변경이 확인되면 route allowlist를 갱신한다.
|
|
76
|
+
|
|
77
|
+
## Done when
|
|
78
|
+
|
|
79
|
+
- KST 기준 날짜와 요청 연월이 일치한다.
|
|
80
|
+
- `k-skill-proxy` route를 통해 호출했고 사용자에게 API key를 요구하지 않았다.
|
|
81
|
+
- 답변에는 `locdate`, `dateName`, `isHoliday`, operation을 함께 명시했다.
|
|
82
|
+
|
|
83
|
+
## Maintainer review notes
|
|
84
|
+
|
|
85
|
+
키 없이 가능한 검증:
|
|
86
|
+
|
|
87
|
+
- `./scripts/validate-skills.sh`
|
|
88
|
+
- `node --test packages/k-skill-proxy/test/server.test.js`
|
|
89
|
+
- `curl -i --get "$KSKILL_PROXY_BASE_URL/v1/korean-holiday/calendar" --data-urlencode "year=2026"` (키 미설정이면 503 확인)
|
|
90
|
+
|
|
91
|
+
Live smoke는 hosted/self-host proxy에 `DATA_GO_KR_API_KEY`가 설정되고 `15012690` 활용신청이 승인된 뒤 수행한다.
|
|
92
|
+
|
|
93
|
+
## Safety notes
|
|
94
|
+
|
|
95
|
+
- 조회 전용 스킬이다.
|
|
96
|
+
- 법정 영업일 판단, 금융/법률 마감 산정에는 원천 API 결과와 관계 법령/기관 공지를 함께 확인한다.
|
|
97
|
+
- 인증키는 프록시 서버에서만 다루며 repo/GitHub Actions/public docs에 저장하지 않는다.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "korean-holiday-calendar",
|
|
3
|
+
"description": "한국천문연구원 특일 정보 API를 k-skill-proxy 경유로 호출해 공휴일·국경일·기념일·24절기·잡절을 조회한다. 조회 전용.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"proxy",
|
|
6
|
+
"lookup"
|
|
7
|
+
],
|
|
8
|
+
"frontmatter": "name: korean-holiday-calendar\ndescription: 한국천문연구원 특일 정보 API를 k-skill-proxy 경유로 호출해 공휴일·국경일·기념일·24절기·잡절을 조회한다. 조회 전용.\nlicense: MIT\nmetadata:\n category: utility\n locale: ko-KR\n phase: v1"
|
|
9
|
+
}
|