@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.
Files changed (350) hide show
  1. package/bin/k-skill.js +60 -0
  2. package/package.json +37 -0
  3. package/skills/assembly-bill-vote-search/instruction.md +111 -0
  4. package/skills/assembly-bill-vote-search/skill.json +10 -0
  5. package/skills/biz-health-check/instruction.md +69 -0
  6. package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
  7. package/skills/biz-health-check/skill.json +10 -0
  8. package/skills/bok-ecos-stats/instruction.md +95 -0
  9. package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
  10. package/skills/bok-ecos-stats/skill.json +10 -0
  11. package/skills/building-register-search/instruction.md +69 -0
  12. package/skills/building-register-search/scripts/building_register.py +309 -0
  13. package/skills/building-register-search/scripts/building_register_xml.py +32 -0
  14. package/skills/building-register-search/skill.json +9 -0
  15. package/skills/bunjang-search/instruction.md +154 -0
  16. package/skills/bunjang-search/skill.json +10 -0
  17. package/skills/catchtable-sniper/instruction.md +270 -0
  18. package/skills/catchtable-sniper/skill.json +9 -0
  19. package/skills/cheap-gas-nearby/instruction.md +107 -0
  20. package/skills/cheap-gas-nearby/skill.json +10 -0
  21. package/skills/corporate-registration-consulting/instruction.md +117 -0
  22. package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
  23. package/skills/corporate-registration-consulting/skill.json +8 -0
  24. package/skills/coupang-product-search/instruction.md +219 -0
  25. package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
  26. package/skills/coupang-product-search/skill.json +10 -0
  27. package/skills/court-auction-notice-search/instruction.md +201 -0
  28. package/skills/court-auction-notice-search/skill.json +9 -0
  29. package/skills/court-payment-order-assistant/instruction.md +120 -0
  30. package/skills/court-payment-order-assistant/skill.json +10 -0
  31. package/skills/d2b-notice-search/instruction.md +117 -0
  32. package/skills/d2b-notice-search/skill.json +10 -0
  33. package/skills/daangn-cars-search/instruction.md +91 -0
  34. package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
  35. package/skills/daangn-cars-search/skill.json +9 -0
  36. package/skills/daangn-jobs-search/instruction.md +90 -0
  37. package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
  38. package/skills/daangn-jobs-search/skill.json +9 -0
  39. package/skills/daangn-realty-search/instruction.md +114 -0
  40. package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
  41. package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
  42. package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
  43. package/skills/daangn-realty-search/skill.json +8 -0
  44. package/skills/daangn-used-goods-search/instruction.md +90 -0
  45. package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
  46. package/skills/daangn-used-goods-search/skill.json +9 -0
  47. package/skills/daishin-report-search/instruction.md +138 -0
  48. package/skills/daishin-report-search/skill.json +10 -0
  49. package/skills/daiso-product-search/instruction.md +172 -0
  50. package/skills/daiso-product-search/skill.json +8 -0
  51. package/skills/danawa-price-search/instruction.md +183 -0
  52. package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
  53. package/skills/danawa-price-search/skill.json +8 -0
  54. package/skills/delivery-tracking/instruction.md +358 -0
  55. package/skills/delivery-tracking/skill.json +8 -0
  56. package/skills/donation-place-search/instruction.md +129 -0
  57. package/skills/donation-place-search/skill.json +9 -0
  58. package/skills/emergency-room-beds/instruction.md +82 -0
  59. package/skills/emergency-room-beds/skill.json +8 -0
  60. package/skills/ev-charger-nearby/instruction.md +80 -0
  61. package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
  62. package/skills/ev-charger-nearby/skill.json +10 -0
  63. package/skills/ev-subsidy-status/instruction.md +165 -0
  64. package/skills/ev-subsidy-status/skill.json +10 -0
  65. package/skills/express-bus-booking/instruction.md +207 -0
  66. package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
  67. package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
  68. package/skills/express-bus-booking/skill.json +8 -0
  69. package/skills/fine-dust-location/instruction.md +89 -0
  70. package/skills/fine-dust-location/skill.json +9 -0
  71. package/skills/flight-ticket-search/instruction.md +237 -0
  72. package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
  73. package/skills/flight-ticket-search/skill.json +9 -0
  74. package/skills/foresttrip-vacancy/instruction.md +167 -0
  75. package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
  76. package/skills/foresttrip-vacancy/skill.json +10 -0
  77. package/skills/fsc-corporate-info/instruction.md +57 -0
  78. package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
  79. package/skills/fsc-corporate-info/skill.json +10 -0
  80. package/skills/g2b-order-plan-search/instruction.md +131 -0
  81. package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
  82. package/skills/g2b-order-plan-search/skill.json +10 -0
  83. package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
  84. package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
  85. package/skills/g2b-sanctioned-supplier/skill.json +10 -0
  86. package/skills/gangnamunni-clinic-search/instruction.md +113 -0
  87. package/skills/gangnamunni-clinic-search/skill.json +9 -0
  88. package/skills/geeknews-search/instruction.md +69 -0
  89. package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
  90. package/skills/geeknews-search/skill.json +8 -0
  91. package/skills/gongsijiga-search/instruction.md +128 -0
  92. package/skills/gongsijiga-search/skill.json +9 -0
  93. package/skills/gov-overseas-trip-report/instruction.md +488 -0
  94. package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
  95. package/skills/gov-overseas-trip-report/skill.json +9 -0
  96. package/skills/han-river-water-level/instruction.md +83 -0
  97. package/skills/han-river-water-level/skill.json +10 -0
  98. package/skills/highway-traffic-status/instruction.md +88 -0
  99. package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
  100. package/skills/highway-traffic-status/skill.json +11 -0
  101. package/skills/hipass-receipt/instruction.md +97 -0
  102. package/skills/hipass-receipt/skill.json +10 -0
  103. package/skills/hola-poke-yeoksam/instruction.md +247 -0
  104. package/skills/hola-poke-yeoksam/skill.json +8 -0
  105. package/skills/household-waste-info/instruction.md +117 -0
  106. package/skills/household-waste-info/skill.json +10 -0
  107. package/skills/housing-official-price/instruction.md +177 -0
  108. package/skills/housing-official-price/skill.json +10 -0
  109. package/skills/hwp/instruction.md +206 -0
  110. package/skills/hwp/skill.json +8 -0
  111. package/skills/intercity-bus-booking/instruction.md +189 -0
  112. package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
  113. package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
  114. package/skills/intercity-bus-booking/skill.json +8 -0
  115. package/skills/iros-registry-automation/instruction.md +229 -0
  116. package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
  117. package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
  118. package/skills/iros-registry-automation/skill.json +9 -0
  119. package/skills/job-posting-match/instruction.md +130 -0
  120. package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
  121. package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
  122. package/skills/job-posting-match/skill.json +8 -0
  123. package/skills/jobkorea-talent-search/instruction.md +118 -0
  124. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
  125. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
  126. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
  127. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
  128. package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
  129. package/skills/jobkorea-talent-search/skill.json +9 -0
  130. package/skills/joseon-sillok-search/instruction.md +76 -0
  131. package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
  132. package/skills/joseon-sillok-search/skill.json +8 -0
  133. package/skills/k-dart/instruction.md +406 -0
  134. package/skills/k-dart/skill.json +8 -0
  135. package/skills/k-schoollunch-menu/instruction.md +109 -0
  136. package/skills/k-schoollunch-menu/skill.json +9 -0
  137. package/skills/k-skill-cleaner/instruction.md +80 -0
  138. package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
  139. package/skills/k-skill-cleaner/skill.json +8 -0
  140. package/skills/k-skill-setup/instruction.md +253 -0
  141. package/skills/k-skill-setup/skill.json +11 -0
  142. package/skills/kakao-bar-nearby/instruction.md +76 -0
  143. package/skills/kakao-bar-nearby/skill.json +8 -0
  144. package/skills/kakao-map/instruction.md +176 -0
  145. package/skills/kakao-map/skill.json +10 -0
  146. package/skills/kakaotalk-mac/instruction.md +189 -0
  147. package/skills/kakaotalk-mac/skill.json +8 -0
  148. package/skills/kbl-results/instruction.md +89 -0
  149. package/skills/kbl-results/skill.json +9 -0
  150. package/skills/kbo-results/instruction.md +82 -0
  151. package/skills/kbo-results/skill.json +8 -0
  152. package/skills/keris-academic-search/instruction.md +81 -0
  153. package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
  154. package/skills/keris-academic-search/skill.json +10 -0
  155. package/skills/kleague-results/instruction.md +92 -0
  156. package/skills/kleague-results/skill.json +8 -0
  157. package/skills/kopis-performance-search/instruction.md +109 -0
  158. package/skills/kopis-performance-search/skill.json +10 -0
  159. package/skills/korea-weather/instruction.md +93 -0
  160. package/skills/korea-weather/skill.json +10 -0
  161. package/skills/korean-character-count/instruction.md +87 -0
  162. package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
  163. package/skills/korean-character-count/skill.json +8 -0
  164. package/skills/korean-cinema-search/instruction.md +177 -0
  165. package/skills/korean-cinema-search/skill.json +8 -0
  166. package/skills/korean-heritage-search/instruction.md +106 -0
  167. package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
  168. package/skills/korean-heritage-search/skill.json +8 -0
  169. package/skills/korean-holiday-calendar/instruction.md +97 -0
  170. package/skills/korean-holiday-calendar/skill.json +9 -0
  171. package/skills/korean-humanizer/instruction.md +389 -0
  172. package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
  173. package/skills/korean-humanizer/skill.json +8 -0
  174. package/skills/korean-jangbu-for/instruction.md +133 -0
  175. package/skills/korean-jangbu-for/scripts/install.sh +237 -0
  176. package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
  177. package/skills/korean-jangbu-for/skill.json +8 -0
  178. package/skills/korean-law-search/instruction.md +126 -0
  179. package/skills/korean-law-search/skill.json +10 -0
  180. package/skills/korean-marathon-schedule/instruction.md +111 -0
  181. package/skills/korean-marathon-schedule/skill.json +9 -0
  182. package/skills/korean-middle-korean/instruction.md +79 -0
  183. package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
  184. package/skills/korean-middle-korean/skill.json +8 -0
  185. package/skills/korean-patent-search/instruction.md +79 -0
  186. package/skills/korean-patent-search/scripts/patent_search.py +409 -0
  187. package/skills/korean-patent-search/skill.json +9 -0
  188. package/skills/korean-privacy-terms/instruction.md +128 -0
  189. package/skills/korean-privacy-terms/scripts/install.sh +108 -0
  190. package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
  191. package/skills/korean-privacy-terms/skill.json +8 -0
  192. package/skills/korean-scholarship-search/instruction.md +317 -0
  193. package/skills/korean-scholarship-search/references/report-format.md +40 -0
  194. package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
  195. package/skills/korean-scholarship-search/references/search-clues.md +58 -0
  196. package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
  197. package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
  198. package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
  199. package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
  200. package/skills/korean-scholarship-search/skill.json +8 -0
  201. package/skills/korean-slang-writing/instruction.md +181 -0
  202. package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
  203. package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
  204. package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
  205. package/skills/korean-slang-writing/skill.json +8 -0
  206. package/skills/korean-spell-check/instruction.md +105 -0
  207. package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
  208. package/skills/korean-spell-check/skill.json +9 -0
  209. package/skills/korean-stock-search/instruction.md +194 -0
  210. package/skills/korean-stock-search/skill.json +10 -0
  211. package/skills/korean-transit-route/instruction.md +113 -0
  212. package/skills/korean-transit-route/skill.json +10 -0
  213. package/skills/kosis-stats/instruction.md +232 -0
  214. package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
  215. package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
  216. package/skills/kosis-stats/skill.json +9 -0
  217. package/skills/kr-whois-lookup/instruction.md +107 -0
  218. package/skills/kr-whois-lookup/skill.json +10 -0
  219. package/skills/kstartup-search/instruction.md +186 -0
  220. package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
  221. package/skills/kstartup-search/skill.json +11 -0
  222. package/skills/ktx-booking/instruction.md +244 -0
  223. package/skills/ktx-booking/skill.json +10 -0
  224. package/skills/lck-analytics/instruction.md +192 -0
  225. package/skills/lck-analytics/scripts/_lib.js +103 -0
  226. package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
  227. package/skills/lck-analytics/scripts/build-match-report.js +44 -0
  228. package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
  229. package/skills/lck-analytics/skill.json +9 -0
  230. package/skills/lh-notice-search/instruction.md +206 -0
  231. package/skills/lh-notice-search/skill.json +10 -0
  232. package/skills/library-book-search/instruction.md +139 -0
  233. package/skills/library-book-search/skill.json +9 -0
  234. package/skills/local-election-candidate-search/instruction.md +77 -0
  235. package/skills/local-election-candidate-search/skill.json +8 -0
  236. package/skills/localdata-business-status/instruction.md +64 -0
  237. package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
  238. package/skills/localdata-business-status/skill.json +8 -0
  239. package/skills/lotto-results/instruction.md +80 -0
  240. package/skills/lotto-results/skill.json +8 -0
  241. package/skills/lovebug-report/instruction.md +185 -0
  242. package/skills/lovebug-report/skill.json +9 -0
  243. package/skills/market-kurly-search/instruction.md +125 -0
  244. package/skills/market-kurly-search/skill.json +8 -0
  245. package/skills/mfds-drug-safety/instruction.md +86 -0
  246. package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
  247. package/skills/mfds-drug-safety/skill.json +10 -0
  248. package/skills/mfds-food-safety/instruction.md +126 -0
  249. package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
  250. package/skills/mfds-food-safety/skill.json +10 -0
  251. package/skills/myrealtrip-search/instruction.md +239 -0
  252. package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
  253. package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
  254. package/skills/myrealtrip-search/skill.json +9 -0
  255. package/skills/naming-house/instruction.md +146 -0
  256. package/skills/naming-house/skill.json +8 -0
  257. package/skills/national-pension-workplace/instruction.md +64 -0
  258. package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
  259. package/skills/national-pension-workplace/skill.json +10 -0
  260. package/skills/naver-ad-performance/instruction.md +108 -0
  261. package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
  262. package/skills/naver-ad-performance/skill.json +9 -0
  263. package/skills/naver-blog-research/instruction.md +128 -0
  264. package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
  265. package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
  266. package/skills/naver-blog-research/scripts/naver_read.py +256 -0
  267. package/skills/naver-blog-research/scripts/naver_search.py +192 -0
  268. package/skills/naver-blog-research/skill.json +8 -0
  269. package/skills/naver-news-search/instruction.md +103 -0
  270. package/skills/naver-news-search/skill.json +10 -0
  271. package/skills/naver-shopping-search/instruction.md +94 -0
  272. package/skills/naver-shopping-search/skill.json +11 -0
  273. package/skills/nhis-care-checkup-search/instruction.md +116 -0
  274. package/skills/nhis-care-checkup-search/skill.json +10 -0
  275. package/skills/nts-business-registration/instruction.md +115 -0
  276. package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
  277. package/skills/nts-business-registration/skill.json +10 -0
  278. package/skills/nts-tax-delinquency/instruction.md +54 -0
  279. package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
  280. package/skills/nts-tax-delinquency/skill.json +8 -0
  281. package/skills/ohou-today-deal/instruction.md +182 -0
  282. package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
  283. package/skills/ohou-today-deal/skill.json +9 -0
  284. package/skills/olive-young-search/instruction.md +154 -0
  285. package/skills/olive-young-search/skill.json +8 -0
  286. package/skills/parking-lot-search/instruction.md +96 -0
  287. package/skills/parking-lot-search/skill.json +9 -0
  288. package/skills/popbill/instruction.md +146 -0
  289. package/skills/popbill/scripts/popbill_cli.py +284 -0
  290. package/skills/popbill/scripts/popbill_registry.py +54 -0
  291. package/skills/popbill/scripts/popbill_safety.py +37 -0
  292. package/skills/popbill/scripts/popbill_templates.py +71 -0
  293. package/skills/popbill/skill.json +9 -0
  294. package/skills/public-restroom-nearby/instruction.md +89 -0
  295. package/skills/public-restroom-nearby/skill.json +9 -0
  296. package/skills/real-estate-search/instruction.md +172 -0
  297. package/skills/real-estate-search/skill.json +10 -0
  298. package/skills/rhwp-advanced/instruction.md +145 -0
  299. package/skills/rhwp-advanced/skill.json +8 -0
  300. package/skills/rhwp-edit/instruction.md +153 -0
  301. package/skills/rhwp-edit/skill.json +8 -0
  302. package/skills/s2b-notice-search/instruction.md +66 -0
  303. package/skills/s2b-notice-search/skill.json +9 -0
  304. package/skills/saju-fortune/instruction.md +177 -0
  305. package/skills/saju-fortune/skill.json +8 -0
  306. package/skills/saramin-talent-search/instruction.md +119 -0
  307. package/skills/saramin-talent-search/skill.json +9 -0
  308. package/skills/seoul-bike/instruction.md +83 -0
  309. package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
  310. package/skills/seoul-bike/skill.json +10 -0
  311. package/skills/seoul-density/instruction.md +109 -0
  312. package/skills/seoul-density/scripts/seoul_density.py +271 -0
  313. package/skills/seoul-density/skill.json +10 -0
  314. package/skills/seoul-subway-arrival/instruction.md +85 -0
  315. package/skills/seoul-subway-arrival/skill.json +9 -0
  316. package/skills/sh-notice-search/instruction.md +150 -0
  317. package/skills/sh-notice-search/skill.json +9 -0
  318. package/skills/srt-booking/instruction.md +181 -0
  319. package/skills/srt-booking/scripts/srt_booking.py +272 -0
  320. package/skills/srt-booking/scripts/srt_seats.py +156 -0
  321. package/skills/srt-booking/skill.json +20 -0
  322. package/skills/subway-lost-property/instruction.md +93 -0
  323. package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
  324. package/skills/subway-lost-property/skill.json +8 -0
  325. package/skills/ticket-availability/instruction.md +175 -0
  326. package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
  327. package/skills/ticket-availability/skill.json +9 -0
  328. package/skills/toss-securities/instruction.md +116 -0
  329. package/skills/toss-securities/skill.json +9 -0
  330. package/skills/used-car-price-search/instruction.md +109 -0
  331. package/skills/used-car-price-search/skill.json +8 -0
  332. package/skills/yebigun-training/instruction.md +177 -0
  333. package/skills/yebigun-training/skill.json +9 -0
  334. package/skills/zipcode-search/instruction.md +159 -0
  335. package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
  336. package/skills/zipcode-search/skill.json +8 -0
  337. package/src/assemble.js +134 -0
  338. package/src/detect.js +18 -0
  339. package/templates/action-account.md +4 -0
  340. package/templates/action-booking.md +5 -0
  341. package/templates/action-commerce.md +5 -0
  342. package/templates/action-communication.md +5 -0
  343. package/templates/action-submission.md +5 -0
  344. package/templates/browser.md +4 -0
  345. package/templates/core.md +5 -0
  346. package/templates/hard-boundary.md +3 -0
  347. package/templates/local.md +2 -0
  348. package/templates/lookup.md +2 -0
  349. package/templates/proxy.md +2 -0
  350. 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
+ }