@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,272 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import base64
6
+ import contextlib
7
+ import io
8
+ import importlib
9
+ import json
10
+ import os
11
+ import sys
12
+ from types import ModuleType
13
+ from typing import Protocol
14
+
15
+ from srt_seats import parse_cars, parse_seats, sort_cars_for_booking, sort_seats_for_booking
16
+
17
+ SRT_SEAT_ENDPOINT = "https://etk.srail.kr/hpg/hra/01/selectPassengerResearchList.do"
18
+ TRAIN_ID_PREFIX = "srt:v1:"
19
+ TRAIN_ID_FIELDS = (
20
+ "train_number",
21
+ "dep_date",
22
+ "dep_time",
23
+ "arr_date",
24
+ "arr_time",
25
+ "train_code",
26
+ "dep_station_code",
27
+ "arr_station_code",
28
+ "dep_station_run_order",
29
+ "arr_station_run_order",
30
+ )
31
+ ROOM_CODE = {"general": "1", "special": "2"}
32
+ ROOM_NAME = {"general": "일반실", "special": "특실"}
33
+
34
+
35
+ class SrtTrainLike(Protocol):
36
+ train_number: str
37
+ dep_date: str
38
+ dep_time: str
39
+ arr_date: str
40
+ arr_time: str
41
+ train_code: str
42
+ train_name: str
43
+ dep_station_code: str
44
+ dep_station_name: str
45
+ arr_station_code: str
46
+ arr_station_name: str
47
+ dep_station_run_order: str
48
+ arr_station_run_order: str
49
+ general_seat_state: str
50
+ special_seat_state: str
51
+ reserve_wait_possible_code: str
52
+
53
+ def general_seat_available(self) -> bool: ...
54
+
55
+ def special_seat_available(self) -> bool: ...
56
+
57
+ def reserve_standby_available(self) -> bool: ...
58
+
59
+
60
+ class ResponseLike(Protocol):
61
+ text: str
62
+
63
+
64
+ class SessionLike(Protocol):
65
+ def get(self, url: str, params: dict[str, str]) -> ResponseLike: ...
66
+
67
+
68
+ class SrtClientLike(Protocol):
69
+ _session: SessionLike
70
+
71
+ def search_train(
72
+ self,
73
+ dep: str,
74
+ arr: str,
75
+ date: str,
76
+ time: str,
77
+ time_limit: str | None = None,
78
+ available_only: bool = True,
79
+ ) -> list[SrtTrainLike]: ...
80
+
81
+
82
+ def load_srt_module() -> ModuleType:
83
+ try:
84
+ return importlib.import_module("SRT")
85
+ except ModuleNotFoundError as exc:
86
+ raise SystemExit("scripts/srt_booking.py requires SRTrain: python3 -m pip install SRTrain")
87
+
88
+
89
+ def build_client(auto_login: bool = False) -> SrtClientLike:
90
+ srt_module = load_srt_module()
91
+ srt_id = os.environ.get("KSKILL_SRT_ID", "")
92
+ srt_pw = os.environ.get("KSKILL_SRT_PASSWORD", "")
93
+ return srt_module.SRT(srt_id, srt_pw, auto_login=auto_login)
94
+
95
+
96
+ def train_id_payload(train: SrtTrainLike) -> dict[str, str]:
97
+ return {field: getattr(train, field) for field in TRAIN_ID_FIELDS}
98
+
99
+
100
+ def build_train_id(train: SrtTrainLike) -> str:
101
+ raw = json.dumps(train_id_payload(train), ensure_ascii=False, separators=(",", ":")).encode()
102
+ encoded = base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
103
+ return f"{TRAIN_ID_PREFIX}{encoded}"
104
+
105
+
106
+ def parse_train_id(train_id: str) -> dict[str, str]:
107
+ if not train_id.startswith(TRAIN_ID_PREFIX):
108
+ raise SystemExit("train_id must start with srt:v1:")
109
+ encoded = train_id.removeprefix(TRAIN_ID_PREFIX)
110
+ padded = encoded + ("=" * ((4 - len(encoded) % 4) % 4))
111
+ try:
112
+ payload = json.loads(base64.urlsafe_b64decode(padded.encode()).decode())
113
+ except (ValueError, json.JSONDecodeError, UnicodeDecodeError) as exc:
114
+ raise SystemExit("train_id is invalid; rerun search and copy a fresh train_id") from exc
115
+ if not isinstance(payload, dict):
116
+ raise SystemExit("train_id is invalid; rerun search and copy a fresh train_id")
117
+ if any(not isinstance(payload.get(field), str) or not payload[field] for field in TRAIN_ID_FIELDS):
118
+ raise SystemExit("train_id is invalid; rerun search and copy a fresh train_id")
119
+ return {field: payload[field] for field in TRAIN_ID_FIELDS}
120
+
121
+
122
+ def find_train_by_id(trains: list[SrtTrainLike], train_id: str) -> SrtTrainLike | None:
123
+ expected = parse_train_id(train_id)
124
+ return next((train for train in trains if train_id_payload(train) == expected), None)
125
+
126
+
127
+ def normalize_train(train: SrtTrainLike, index: int) -> dict[str, str | bool | int]:
128
+ return {
129
+ "index": index,
130
+ "train_id": build_train_id(train),
131
+ "train_no": train.train_number,
132
+ "train_type": train.train_name,
133
+ "dep_name": train.dep_station_name,
134
+ "dep_date": train.dep_date,
135
+ "dep_time": train.dep_time,
136
+ "arr_name": train.arr_station_name,
137
+ "arr_date": train.arr_date,
138
+ "arr_time": train.arr_time,
139
+ "has_general_seat": train.general_seat_available(),
140
+ "has_special_seat": train.special_seat_available(),
141
+ "has_waiting_list": train.reserve_standby_available(),
142
+ }
143
+
144
+
145
+ def seat_page_params(train: SrtTrainLike, room: str, car_no: int | None) -> dict[str, str]:
146
+ return {
147
+ "runDt1": train.dep_date,
148
+ "dptDt1": train.dep_date,
149
+ "dptTm1": train.dep_time,
150
+ "trnNo1": f"{int(train.train_number):05d}",
151
+ "trnGpCd1": "300",
152
+ "dptRsStnCd1": train.dep_station_code,
153
+ "arvRsStnCd1": train.arr_station_code,
154
+ "dptStnRunOrdr1": train.dep_station_run_order,
155
+ "arvStnRunOrdr1": train.arr_station_run_order,
156
+ "seatAttCd1": "015",
157
+ "psrmClCd1": ROOM_CODE[room],
158
+ "index1": "0",
159
+ "scarNo1": "" if car_no is None else f"{car_no:04d}",
160
+ "chtnDvCd": "1",
161
+ "jrnySqno": "001",
162
+ "mode": "1",
163
+ "psgNum": "1",
164
+ "pageId": "",
165
+ }
166
+
167
+
168
+ def fetch_seat_page(client: SrtClientLike, train: SrtTrainLike, room: str, car_no: int | None) -> str:
169
+ with contextlib.redirect_stdout(io.StringIO()):
170
+ response = client._session.get(SRT_SEAT_ENDPOINT, params=seat_page_params(train, room, car_no))
171
+ return response.text
172
+
173
+
174
+ def command_search(args: argparse.Namespace) -> None:
175
+ client = build_client(auto_login=False)
176
+ with contextlib.redirect_stdout(io.StringIO()):
177
+ trains = client.search_train(args.dep, args.arr, args.date, args.time, args.time_limit, args.available_only)
178
+ print_json({"count": len(trains[: args.limit]), "trains": [normalize_train(train, index) for index, train in enumerate(trains[: args.limit], 1)]})
179
+
180
+
181
+ def command_seats(args: argparse.Namespace) -> None:
182
+ client = build_client(auto_login=False)
183
+ with contextlib.redirect_stdout(io.StringIO()):
184
+ trains = client.search_train(args.dep, args.arr, args.date, args.time, args.time_limit, available_only=False)
185
+ train = find_train_by_id(trains, args.train_id)
186
+ if train is None:
187
+ raise SystemExit("train_id no longer matches any current search result; rerun search and choose a fresh train_id")
188
+
189
+ initial_html = fetch_seat_page(client, train, args.room, args.car_no)
190
+ cars = [car for car in parse_cars(initial_html) if car["room_class"] == ROOM_NAME[args.room]]
191
+ if args.car_no is not None:
192
+ cars = [car for car in cars if car["car_no"] == args.car_no]
193
+ else:
194
+ cars = [car for car in cars if car["available"]]
195
+ if not cars:
196
+ raise SystemExit(f"seat car data is unavailable for {args.room}; retry search or choose another train")
197
+
198
+ car_payloads: list[dict[str, object]] = []
199
+ for car in sort_cars_for_booking(cars, args.car_priority):
200
+ html = initial_html if args.car_no == car["car_no"] else fetch_seat_page(client, train, args.room, car["car_no"])
201
+ seats = parse_seats(html)
202
+ if args.seat:
203
+ seats = [seat for seat in seats if seat["seat"] == args.seat]
204
+ seats = sort_seats_for_booking(seats, args.seat_priority)
205
+ if args.available_only:
206
+ seats = [seat for seat in seats if seat["available"]]
207
+ available_seats = [seat for seat in seats if seat["available"]]
208
+ limited = seats[: args.limit]
209
+ payload = dict(car)
210
+ payload["available_seat_count"] = len(available_seats)
211
+ payload["available_seats"] = [seat["seat"] for seat in available_seats]
212
+ payload["shown_seat_count"] = len(limited)
213
+ payload["seats"] = limited
214
+ if args.seat:
215
+ payload["requested_seat"] = args.seat
216
+ payload["requested_seat_available"] = any(seat["available"] for seat in seats)
217
+ car_payloads.append(payload)
218
+
219
+ print_json({
220
+ "train": normalize_train(train, 1),
221
+ "room": args.room,
222
+ "available_only": args.available_only,
223
+ "car_priority": args.car_priority,
224
+ "seat_priority": args.seat_priority,
225
+ "cars": car_payloads,
226
+ })
227
+
228
+
229
+ def print_json(payload: dict[str, object]) -> None:
230
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
231
+
232
+
233
+ def build_parser() -> argparse.ArgumentParser:
234
+ parser = argparse.ArgumentParser(description="SRT search and seat lookup helper for k-skill")
235
+ subparsers = parser.add_subparsers(dest="command", required=True)
236
+ search = subparsers.add_parser("search", help="SRT 열차를 조회합니다")
237
+ add_trip_args(search)
238
+ search.add_argument("--time-limit", default=None, help="조회 종료 시각 HHMMSS")
239
+ search.add_argument("--available-only", action="store_true", default=False, help="예약 가능한 열차만 출력")
240
+ search.add_argument("--limit", type=int, default=5, help="출력할 최대 열차 수")
241
+ search.set_defaults(func=command_search)
242
+
243
+ seats = subparsers.add_parser("seats", help="SRT 호차별 좌석번호를 조회합니다")
244
+ add_trip_args(seats)
245
+ seats.add_argument("--train-id", required=True, help="search 결과에서 복사한 stable train_id")
246
+ seats.add_argument("--time-limit", default=None, help="조회 종료 시각 HHMMSS")
247
+ seats.add_argument("--room", choices=sorted(ROOM_CODE), default="general")
248
+ seats.add_argument("--car-no", type=int, default=None, help="특정 호차만 조회")
249
+ seats.add_argument("--seat", default=None, help="특정 좌석번호만 조회, 예: 6C")
250
+ seats.add_argument("--available-only", action="store_true", help="빈 좌석만 출력")
251
+ seats.add_argument("--car-priority", choices=("center", "low", "high"), default="center")
252
+ seats.add_argument("--seat-priority", choices=("forward-window", "window-forward", "row-low"), default="forward-window")
253
+ seats.add_argument("--limit", type=int, default=100, help="호차별 출력할 최대 좌석 수")
254
+ seats.set_defaults(func=command_seats)
255
+ return parser
256
+
257
+
258
+ def add_trip_args(parser: argparse.ArgumentParser) -> None:
259
+ parser.add_argument("dep", help="출발역")
260
+ parser.add_argument("arr", help="도착역")
261
+ parser.add_argument("date", help="출발일 YYYYMMDD")
262
+ parser.add_argument("time", help="희망 시작 시각 HHMMSS")
263
+
264
+
265
+ def main(argv: list[str] | None = None) -> int:
266
+ args = build_parser().parse_args(argv)
267
+ args.func(args)
268
+ return 0
269
+
270
+
271
+ if __name__ == "__main__":
272
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from typing import TypedDict
6
+
7
+
8
+ class SrtCar(TypedDict):
9
+ car_no: int
10
+ car_no_raw: str
11
+ room_class: str
12
+ available: bool
13
+ current: bool
14
+
15
+
16
+ class SrtSeat(TypedDict):
17
+ seat: str
18
+ seat_no: str
19
+ available: bool
20
+ direction: str
21
+ position: str
22
+ notes: list[str]
23
+
24
+
25
+ CAR_RE = re.compile(
26
+ r'<li class="scar-(?P<car>\d+)(?P<class>[^"]*)">(?P<body>.*?)</li>',
27
+ re.DOTALL,
28
+ )
29
+ SEAT_LINK_RE = re.compile(
30
+ r"<a[^>]+selectSeatInfo\(this,\s*'(?P<seat_no>[^']+)',\s*'(?P<seat>[^']+)'\)[^>]*>"
31
+ r".*?<em>\((?P<detail>[^)]*)\)</em>",
32
+ re.DOTALL,
33
+ )
34
+ SEAT_SPAN_RE = re.compile(
35
+ r"<span>\s*(?P<seat>\d+[A-Z])\s*<strong><em>\((?P<detail>[^)]*)\)</em></strong></span>",
36
+ re.DOTALL,
37
+ )
38
+ TAG_RE = re.compile(r"<[^>]+>")
39
+
40
+
41
+ def strip_tags(value: str) -> str:
42
+ return TAG_RE.sub(" ", value).replace("\xa0", " ").strip()
43
+
44
+
45
+ def parse_detail(detail: str) -> tuple[str, str, list[str]]:
46
+ parts = [part.strip() for part in detail.split(",")]
47
+ direction = next((part for part in parts if part in {"정방향", "역방향"}), "unknown")
48
+ position = next((part for part in parts if part in {"창측", "내측", "1인석"}), "unknown")
49
+ notes = [part for part in parts if part not in {direction, position} and part]
50
+ return direction, position, notes
51
+
52
+
53
+ def parse_cars(html: str) -> list[SrtCar]:
54
+ cars: list[SrtCar] = []
55
+ for match in CAR_RE.finditer(html):
56
+ body = match.group("body")
57
+ text = strip_tags(body)
58
+ room_class = "특실" if "특실" in text else "일반실"
59
+ css_class = match.group("class")
60
+ has_link = "selectScarInfo" in body
61
+ cars.append(
62
+ {
63
+ "car_no": int(match.group("car")),
64
+ "car_no_raw": f"{int(match.group('car')):04d}",
65
+ "room_class": room_class,
66
+ "available": has_link and "off" not in css_class.split(),
67
+ "current": "on" in css_class.split(),
68
+ }
69
+ )
70
+ return cars
71
+
72
+
73
+ def parse_seats(html: str) -> list[SrtSeat]:
74
+ seats: list[SrtSeat] = []
75
+ seen: set[str] = set()
76
+ for match in SEAT_LINK_RE.finditer(html):
77
+ direction, position, notes = parse_detail(match.group("detail"))
78
+ seat = match.group("seat")
79
+ seen.add(seat)
80
+ seats.append(
81
+ {
82
+ "seat": seat,
83
+ "seat_no": match.group("seat_no"),
84
+ "available": True,
85
+ "direction": direction,
86
+ "position": position,
87
+ "notes": notes,
88
+ }
89
+ )
90
+ for match in SEAT_SPAN_RE.finditer(html):
91
+ seat = match.group("seat")
92
+ if seat in seen:
93
+ continue
94
+ direction, position, notes = parse_detail(match.group("detail"))
95
+ seats.append(
96
+ {
97
+ "seat": seat,
98
+ "seat_no": "",
99
+ "available": False,
100
+ "direction": direction,
101
+ "position": position,
102
+ "notes": notes,
103
+ }
104
+ )
105
+ return seats
106
+
107
+
108
+ def parse_seat_label(seat_label: str) -> tuple[int | None, str]:
109
+ match = re.match(r"^(\d+)([A-Z])$", seat_label)
110
+ if match is None:
111
+ return None, ""
112
+ return int(match.group(1)), match.group(2)
113
+
114
+
115
+ def car_center_priority(car: SrtCar, car_numbers: list[int]) -> tuple[float, int]:
116
+ if not car_numbers:
117
+ return (0.0, car["car_no"])
118
+ center = (min(car_numbers) + max(car_numbers)) / 2
119
+ return (abs(car["car_no"] - center), car["car_no"])
120
+
121
+
122
+ def sort_cars_for_booking(cars: list[SrtCar], priority: str = "center") -> list[SrtCar]:
123
+ match priority:
124
+ case "center":
125
+ car_numbers = [car["car_no"] for car in cars]
126
+ return sorted(cars, key=lambda car: car_center_priority(car, car_numbers))
127
+ case "low":
128
+ return sorted(cars, key=lambda car: car["car_no"])
129
+ case "high":
130
+ return sorted(cars, key=lambda car: car["car_no"], reverse=True)
131
+ case _:
132
+ raise ValueError(f"unsupported car priority: {priority}")
133
+
134
+
135
+ def seat_preference_key(seat: SrtSeat, priority: str = "forward-window") -> tuple[int, int, int, str]:
136
+ row, column = parse_seat_label(seat["seat"])
137
+ forward_rank = 0 if seat["direction"] == "정방향" else 1
138
+ window_rank = 0 if seat["position"] in {"창측", "1인석"} else 1
139
+ row_rank = 999 if row is None else row
140
+ match priority:
141
+ case "forward-window":
142
+ return (forward_rank, window_rank, row_rank, column)
143
+ case "window-forward":
144
+ return (window_rank, forward_rank, row_rank, column)
145
+ case "row-low":
146
+ return (row_rank, forward_rank, window_rank, column)
147
+ case _:
148
+ raise ValueError(f"unsupported seat priority: {priority}")
149
+
150
+
151
+ def sort_seats_for_booking(seats: list[SrtSeat], priority: str = "forward-window") -> list[SrtSeat]:
152
+ return sorted(seats, key=lambda seat: seat_preference_key(seat, priority))
153
+
154
+
155
+ sort_cars = sort_cars_for_booking
156
+ sort_seats = sort_seats_for_booking
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "srt-booking",
3
+ "description": "Search, reserve, inspect, and cancel SRT tickets in Korea with the SRTrain library. Use when the user asks for SRT seat availability, booking, canceling, or sold-out retry plans. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
4
+ "profiles": [
5
+ "vault",
6
+ "browser",
7
+ "action:booking"
8
+ ],
9
+ "frontmatter": "name: srt-booking\ndescription: Search, reserve, inspect, and cancel SRT tickets in Korea with the SRTrain library. Use when the user asks for SRT seat availability, booking, canceling, or sold-out retry plans. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: travel\n locale: ko-KR\n phase: v1",
10
+ "bundle": [
11
+ {
12
+ "from": "scripts/srt_booking.py",
13
+ "to": "scripts/srt_booking.py"
14
+ },
15
+ {
16
+ "from": "scripts/srt_seats.py",
17
+ "to": "scripts/srt_seats.py"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,93 @@
1
+ # Subway Lost Property
2
+
3
+ ## What this skill does
4
+
5
+ 지하철에서 잃어버린 물건을 찾기 위해 공식 경로를 구조화한다.
6
+
7
+ - LOST112 `습득물 목록 조회`에 넣을 검색 조건을 정리한다.
8
+ - 서울교통공사 유실물센터 진입점을 함께 안내한다.
9
+ - 공개 API가 명확하지 않은 상태라 v1은 **안내형/하이브리드** 범위로 유지한다.
10
+
11
+ ## When to use
12
+
13
+ - "강남역에서 지갑 잃어버렸는데 어디서 찾아?"
14
+ - "2호선 지하철 분실물 조회 방법 알려줘"
15
+ - "서울 지하철 유실물 공식 사이트로 바로 찾게 도와줘"
16
+
17
+ ## Inputs
18
+
19
+ - 필수: 역명 또는 보관장소 키워드
20
+ - 선택: 물품명, 호선, 분실/습득 추정 기간
21
+
22
+ ## Official surfaces
23
+
24
+ - LOST112 습득물 목록: `https://www.lost112.go.kr/find/findList.do`
25
+ - 서울교통공사 유실물센터: `https://www.seoulmetro.co.kr/kr/page.do?menuIdx=541`
26
+
27
+ LOST112 검색 폼에서 확인되는 핵심 필드는 아래와 같다.
28
+
29
+ - `SITE=V`: 경찰 이외 기관(지하철, 공항 등)
30
+ - `DEP_PLACE`: 보관장소
31
+ - `PRDT_NM`: 습득물명
32
+ - `START_YMD`, `END_YMD`: 검색 기간
33
+
34
+ ## Workflow
35
+
36
+ ### 1) Ask for the minimum clues first
37
+
38
+ 바로 추정하지 말고 아래 정보를 먼저 받는다.
39
+
40
+ - 어느 역/어느 구간인지
41
+ - 물건 종류가 무엇인지
42
+ - 대략 언제 잃어버렸는지
43
+ - 서울교통공사(1~8호선) 범위인지, 다른 운영사인지
44
+
45
+ ### 2) Generate the official LOST112 search payload
46
+
47
+ repo helper를 그대로 써도 된다.
48
+
49
+ ```bash
50
+ python3 scripts/subway_lost_property.py \
51
+ --station 강남역 \
52
+ --item 지갑 \
53
+ --days 14
54
+ ```
55
+
56
+ helper는 기본적으로 `SITE=V` 를 사용하고, 역명/물품명/기간을 LOST112 form payload와 **referer까지 포함한 runnable `curl` 예시**로 정리해 준다. 예시 `curl` 은 느린 공식 응답을 감안해 `--max-time 60` 을 포함하고, 응답 HTML을 `lost112-search-result.html` 로 저장한다.
57
+
58
+ ### 3) Optionally verify live reachability
59
+
60
+ ```bash
61
+ python3 scripts/subway_lost_property.py \
62
+ --station 강남역 \
63
+ --item 지갑 \
64
+ --days 14 \
65
+ --verify-live
66
+ ```
67
+
68
+ `--verify-live` 는 공식 페이지 접근 가능 여부만 보수적으로 확인한다. 사이트가 느리면 timeout을 그대로 보고하고 manual open으로 전환한다.
69
+
70
+ ### 4) Guide the user conservatively
71
+
72
+ - 먼저 LOST112에서 역명 그대로 검색
73
+ - 결과가 없으면 `강남`처럼 `역` 없는 키워드로 재검색
74
+ - 필요하면 호선명도 추가 검색
75
+ - 서울교통공사 유실물센터 페이지를 함께 열어 후속 안내 확인
76
+
77
+ ## Done when
78
+
79
+ - 사용자가 공식 조회 경로를 바로 열 수 있다.
80
+ - LOST112 검색 조건(`SITE=V`, 역명, 물품명, 기간)을 받았다.
81
+ - 자동 조회 보장 범위와 manual fallback을 분명히 설명했다.
82
+
83
+ ## Failure modes
84
+
85
+ - 공식 사이트 응답이 느리거나 timeout 발생
86
+ - 역명이 실제 보관장소 표기와 달라 검색 결과가 비는 경우
87
+ - 공개 API 부재로 자동 결과 수집이 안정적이지 않은 경우
88
+
89
+ ## Notes
90
+
91
+ - v1은 공식 웹 흐름을 안전하게 안내하는 범위다.
92
+ - 완전 자동 조회형으로 확장하려면 캡차/세션/동적 요청 안정성 재검증이 먼저 필요하다.
93
+ - helper는 공식 HTTPS 진입점만 사용한다.