@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,309 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only building-register title lookup helper using stdlib only."""
3
+ # allow: SIZE_OK - Cohesive Building Register CLI adapter covering normalization, transport, and output contracts.
4
+
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import json
9
+ import os
10
+ import pathlib
11
+ import sys
12
+ import urllib.error
13
+ import urllib.parse
14
+ import urllib.request
15
+ from typing import Any, Dict, Optional
16
+
17
+ SCRIPTS_DIR = pathlib.Path(__file__).resolve().parent
18
+ if str(SCRIPTS_DIR) not in sys.path:
19
+ sys.path.insert(0, str(SCRIPTS_DIR))
20
+ from building_register_xml import parse_direct_xml
21
+
22
+
23
+ DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
24
+ DEFAULT_SECRETS_PATH = pathlib.Path("~/.config/k-skill/secrets.env").expanduser()
25
+ UPSTREAM_URL = "https://apis.data.go.kr/1613000/BldRgstHubService/getBrTitleInfo"
26
+ PROXY_DOWN_MSG = "설정된 k-skill-proxy 프록시 서버가 응답하지 않습니다. 잠시 후 재시도하거나 운영자에게 문의하세요."
27
+ PROXY_NOT_CONFIGURED_MSG = "k-skill-proxy에 건축물대장 API 키가 설정되어 있지 않습니다. 운영자에게 문의하세요."
28
+ KAKAO_NOT_CONFIGURED_MSG = (
29
+ "k-skill-proxy에 Kakao Local API 키가 설정되어 있지 않습니다. "
30
+ "주소 조회는 Kakao geocode가 필요합니다. 운영자에게 문의하거나 PNU/개별 필지를 사용하세요."
31
+ )
32
+
33
+
34
+ class HelperError(RuntimeError):
35
+ pass
36
+
37
+
38
+ def load_secrets(path: pathlib.Path) -> Dict[str, str]:
39
+ values: Dict[str, str] = {}
40
+ try:
41
+ lines = path.read_text(encoding="utf-8").splitlines()
42
+ except OSError:
43
+ return values
44
+ for raw in lines:
45
+ line = raw.strip()
46
+ if not line or line.startswith("#") or "=" not in line:
47
+ continue
48
+ key, value = line.split("=", 1)
49
+ values[key.strip()] = value.strip().strip('"').strip("'")
50
+ return values
51
+
52
+
53
+ def resolve_api_key(args: argparse.Namespace) -> Optional[str]:
54
+ env_key = os.environ.get("KSKILL_BUILDING_REGISTER_API_KEY") or os.environ.get("DATA_GO_KR_API_KEY")
55
+ if env_key and env_key.strip():
56
+ return env_key.strip()
57
+ secrets = load_secrets(pathlib.Path(args.secrets_path).expanduser())
58
+ value = secrets.get("KSKILL_BUILDING_REGISTER_API_KEY") or secrets.get("DATA_GO_KR_API_KEY")
59
+ return value.strip() if value and value.strip() else None
60
+
61
+
62
+ def _bounded_integer(value: int, label: str, maximum: int) -> int:
63
+ if value < 1 or value > maximum:
64
+ raise HelperError(f"{label} 값은 1~{maximum} 범위여야 합니다.")
65
+ return value
66
+
67
+
68
+ def _exact_digits(value: Optional[str], length: int, label: str) -> str:
69
+ text = (value or "").strip()
70
+ if len(text) != length or not text.isdigit():
71
+ raise HelperError(f"{label} 값은 {length}자리 숫자여야 합니다.")
72
+ return text
73
+
74
+
75
+ def _parcel(value: Optional[str], label: str, required: bool = False) -> str:
76
+ text = (value or "").strip()
77
+ if not text:
78
+ if required:
79
+ raise HelperError(f"{label} 값이 필요합니다.")
80
+ return "0000"
81
+ if not text.isdigit() or len(text) > 4:
82
+ raise HelperError(f"{label} 값은 1~4자리 숫자여야 합니다.")
83
+ return text.zfill(4)
84
+
85
+
86
+ def normalize_parcel(pnu: Optional[str], sigungu_cd: Optional[str], bjdong_cd: Optional[str],
87
+ plat_gb_cd: Optional[str], bun: Optional[str], ji: Optional[str]) -> Dict[str, str]:
88
+ explicit = any((sigungu_cd, bjdong_cd, plat_gb_cd, bun, ji))
89
+ if pnu and explicit:
90
+ raise HelperError("PNU와 개별 법정동/필지 값은 같이 입력할 수 없습니다.")
91
+ if pnu:
92
+ normalized_pnu = _exact_digits(pnu, 19, "PNU")
93
+ land_category = normalized_pnu[10:11]
94
+ plat_gb_cd_by_land_category = {"1": "0", "2": "1"}
95
+ if land_category not in plat_gb_cd_by_land_category:
96
+ raise HelperError("PNU 토지구분 값은 1(일반 토지) 또는 2(산)여야 합니다.")
97
+ result = {
98
+ "pnu": normalized_pnu,
99
+ "sigunguCd": normalized_pnu[:5],
100
+ "bjdongCd": normalized_pnu[5:10],
101
+ "platGbCd": plat_gb_cd_by_land_category[land_category],
102
+ "bun": normalized_pnu[11:15],
103
+ "ji": normalized_pnu[15:19],
104
+ }
105
+ return result
106
+ if not explicit:
107
+ raise HelperError("--pnu, --address 또는 개별 법정동/필지 값을 입력하세요.")
108
+ sigungu = _exact_digits(sigungu_cd, 5, "sigunguCd")
109
+ bjdong = _exact_digits(bjdong_cd, 5, "bjdongCd")
110
+ plat = (plat_gb_cd or "").strip()
111
+ if plat not in {"0", "1", "2"}:
112
+ raise HelperError("platGbCd 값은 0, 1, 2 중 하나여야 합니다.")
113
+ main = _parcel(bun, "bun", required=True)
114
+ sub = _parcel(ji, "ji")
115
+ result = {
116
+ "sigunguCd": sigungu,
117
+ "bjdongCd": bjdong,
118
+ "platGbCd": plat,
119
+ "bun": main,
120
+ "ji": sub,
121
+ }
122
+ land_category_by_plat_gb_cd = {"0": "1", "1": "2"}
123
+ land_category = land_category_by_plat_gb_cd.get(plat)
124
+ if land_category is not None:
125
+ result["pnu"] = f"{sigungu}{bjdong}{land_category}{main}{sub}"
126
+ return result
127
+
128
+
129
+ def build_query(args: argparse.Namespace) -> Dict[str, Any]:
130
+ if args.address:
131
+ if args.direct:
132
+ raise HelperError("--address는 Kakao hosted proxy를 사용하는 proxy mode에서만 지원합니다. 직접 호출은 PNU나 개별 필지 값을 사용하세요.")
133
+ if any((args.pnu, args.sigungu_cd, args.bjdong_cd, args.plat_gb_cd, args.bun, args.ji)):
134
+ raise HelperError("--address와 PNU/개별 필지 값은 같이 입력할 수 없습니다.")
135
+ raise HelperError("주소를 먼저 법정동 코드와 필지로 변환해야 합니다.")
136
+ query: Dict[str, Any] = normalize_parcel(
137
+ args.pnu, args.sigungu_cd, args.bjdong_cd, args.plat_gb_cd, args.bun, args.ji
138
+ )
139
+ query["pageNo"] = _bounded_integer(args.page_no, "pageNo", 100000)
140
+ query["numOfRows"] = _bounded_integer(args.num_of_rows, "numOfRows", 100)
141
+ return query
142
+
143
+
144
+ def query_from_address_payload(payload: Dict[str, Any]) -> Dict[str, str]:
145
+ documents = payload.get("documents")
146
+ if not isinstance(documents, list) or len(documents) != 1:
147
+ raise HelperError("주소 검색 결과가 없거나 하나로 확정되지 않았습니다. 법정동 주소와 필지 번호를 더 정확히 입력하세요.")
148
+ address = documents[0].get("address") if isinstance(documents[0], dict) else None
149
+ if not isinstance(address, dict):
150
+ raise HelperError("주소 결과에 법정동 필지 정보가 없습니다. 도로명만이 아니라 번지까지 포함한 주소를 입력하세요.")
151
+ b_code = str(address.get("b_code") or "").strip()
152
+ main = str(address.get("main_address_no") or "").strip()
153
+ sub = str(address.get("sub_address_no") or "0").strip() or "0"
154
+ mountain = str(address.get("mountain_yn") or "N").strip().upper()
155
+ if len(b_code) != 10 or not b_code.isdigit():
156
+ raise HelperError("주소 결과에 10자리 법정동 코드(b_code)가 없습니다.")
157
+ if not main:
158
+ raise HelperError("주소 결과에 본번이 없어 필지를 확정할 수 없습니다.")
159
+ return normalize_parcel(None, b_code[:5], b_code[5:], "1" if mountain == "Y" else "0", main, sub)
160
+
161
+
162
+ def build_title_url(args: argparse.Namespace, query: Dict[str, Any], api_key: Optional[str]) -> str:
163
+ if args.direct:
164
+ if not api_key:
165
+ raise HelperError(
166
+ "KSKILL_BUILDING_REGISTER_API_KEY 또는 DATA_GO_KR_API_KEY가 없습니다. "
167
+ "공공데이터포털 데이터셋 15134735 활용신청 후 키를 설정하세요."
168
+ )
169
+ direct_query = {key: value for key, value in query.items() if key != "pnu"}
170
+ direct_query["serviceKey"] = api_key
171
+ return f"{UPSTREAM_URL}?{urllib.parse.urlencode(direct_query)}"
172
+ if args.pnu:
173
+ proxy_query = {key: value for key, value in query.items() if key not in {"sigunguCd", "bjdongCd", "platGbCd", "bun", "ji"}}
174
+ else:
175
+ proxy_query = {key: value for key, value in query.items() if key != "pnu"}
176
+ return f"{args.proxy_base_url.rstrip('/')}/v1/building-register/title?{urllib.parse.urlencode(proxy_query)}"
177
+
178
+
179
+ def http_get_json(url: str, timeout: int, via_proxy: bool) -> Dict[str, Any]:
180
+ request = urllib.request.Request(url, headers={"accept": "application/json", "user-agent": "k-skill/building-register-search"})
181
+ try:
182
+ with urllib.request.urlopen(request, timeout=timeout) as response:
183
+ raw = response.read().decode("utf-8", errors="replace")
184
+ except urllib.error.HTTPError as error:
185
+ raw = error.read().decode("utf-8", errors="replace") if error.fp else ""
186
+ try:
187
+ payload = json.loads(raw)
188
+ except json.JSONDecodeError:
189
+ payload = {}
190
+ if via_proxy and error.code == 503 and payload.get("error") == "upstream_not_configured":
191
+ message = str(payload.get("message") or "")
192
+ if "KAKAO" in message.upper() or "/kakao-local/" in url:
193
+ raise HelperError(KAKAO_NOT_CONFIGURED_MSG) from error
194
+ raise HelperError(PROXY_NOT_CONFIGURED_MSG) from error
195
+ raise HelperError(str(payload.get("message") or f"API HTTP 오류: {error.code} {error.reason}")) from error
196
+ except urllib.error.URLError as error:
197
+ if via_proxy:
198
+ raise HelperError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
199
+ raise HelperError(f"상류 API 네트워크 오류: {error.reason}") from error
200
+ except TimeoutError as error:
201
+ target = "프록시 서버" if via_proxy else "상류 API"
202
+ raise HelperError(f"{target} 요청 시간이 초과되었습니다.") from error
203
+ try:
204
+ payload = json.loads(raw)
205
+ except json.JSONDecodeError as error:
206
+ raise HelperError("API 응답이 올바른 JSON이 아닙니다.") from error
207
+ if not isinstance(payload, dict):
208
+ raise HelperError("API 응답 형식이 올바르지 않습니다.")
209
+ return payload
210
+
211
+
212
+ def http_get_direct_xml(url: str, timeout: int) -> Dict[str, Any]:
213
+ request = urllib.request.Request(url, headers={"accept": "application/xml", "user-agent": "k-skill/building-register-search"})
214
+ try:
215
+ with urllib.request.urlopen(request, timeout=timeout) as response:
216
+ raw = response.read()
217
+ except urllib.error.HTTPError as error:
218
+ raise HelperError(f"건축물대장 API HTTP 오류: {error.code} {error.reason}") from error
219
+ except urllib.error.URLError as error:
220
+ raise HelperError(f"건축물대장 API 네트워크 오류: {error.reason}") from error
221
+ except TimeoutError as error:
222
+ raise HelperError("건축물대장 API 요청 시간이 초과되었습니다.") from error
223
+ return parse_direct_xml(raw, HelperError)
224
+
225
+
226
+ def format_text(payload: Dict[str, Any]) -> str:
227
+ items = payload.get("items") or []
228
+ if not isinstance(items, list):
229
+ items = [items]
230
+ total = payload.get("total_count", len(items))
231
+ lines = [f"건축물대장 표제부 조회 결과: {total}건"]
232
+ for item in items:
233
+ address = item.get("platPlc") or item.get("newPlatPlc") or "주소 없음"
234
+ purpose = item.get("mainPurpsCdNm") or "주용도 없음"
235
+ area = item.get("totArea") or "-"
236
+ floors = f"지상 {item.get('grndFlrCnt', '-')} / 지하 {item.get('ugrndFlrCnt', '-')}"
237
+ approved = item.get("useAprDay") or "-"
238
+ lines.append(f"- {address} / {purpose} / 연면적 {area}㎡ / {floors} / 사용승인일 {approved}")
239
+ if not items:
240
+ lines.append("해당 필지의 건축물대장 표제부가 없습니다.")
241
+ return "\n".join(lines)
242
+
243
+
244
+ def build_parser() -> argparse.ArgumentParser:
245
+ parser = argparse.ArgumentParser(description="건축물대장 표제부 조회")
246
+ subparsers = parser.add_subparsers(dest="command", required=True)
247
+ title = subparsers.add_parser("title", help="건축물대장 표제부 조회")
248
+ title.add_argument("--pnu")
249
+ title.add_argument("--sigungu-cd")
250
+ title.add_argument("--bjdong-cd")
251
+ title.add_argument("--plat-gb-cd")
252
+ title.add_argument("--bun")
253
+ title.add_argument("--ji")
254
+ title.add_argument("--address")
255
+ title.add_argument("--page-no", type=int, default=1)
256
+ title.add_argument("--num-of-rows", type=int, default=10)
257
+ title.add_argument("--proxy-base-url", default=os.environ.get("KSKILL_PROXY_BASE_URL", DEFAULT_PROXY_BASE_URL))
258
+ title.add_argument("--direct", action="store_true")
259
+ title.add_argument("--secrets-path", default=str(DEFAULT_SECRETS_PATH))
260
+ title.add_argument("--timeout", type=int, default=20)
261
+ title.add_argument("--dry-run", action="store_true")
262
+ title.add_argument("--json", action="store_true")
263
+ return parser
264
+
265
+
266
+ def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace:
267
+ return build_parser().parse_args(argv)
268
+
269
+
270
+ def run(argv: Optional[list[str]] = None) -> int:
271
+ try:
272
+ args = parse_args(argv)
273
+ if args.address:
274
+ if args.direct:
275
+ raise HelperError("--address는 proxy mode에서만 지원합니다. 직접 호출은 PNU나 개별 필지 값을 사용하세요.")
276
+ if any((args.pnu, args.sigungu_cd, args.bjdong_cd, args.plat_gb_cd, args.bun, args.ji)):
277
+ raise HelperError("--address와 PNU/개별 필지 값은 같이 입력할 수 없습니다.")
278
+ geocode_url = f"{args.proxy_base_url.rstrip('/')}/v1/kakao-local/geocode?{urllib.parse.urlencode({'q': args.address, 'limit': 2})}"
279
+ if args.dry_run:
280
+ print(json.dumps({
281
+ "operation": "title",
282
+ "geocode_url": geocode_url,
283
+ "next": (
284
+ "/v1/building-register/title?sigunguCd=<derived>&bjdongCd=<derived>"
285
+ "&platGbCd=<derived>&bun=<derived>&ji=<derived>"
286
+ ),
287
+ }, ensure_ascii=False, indent=2))
288
+ return 0
289
+ address_payload = http_get_json(geocode_url, args.timeout, via_proxy=True)
290
+ query = query_from_address_payload(address_payload)
291
+ query["pageNo"] = _bounded_integer(args.page_no, "pageNo", 100000)
292
+ query["numOfRows"] = _bounded_integer(args.num_of_rows, "numOfRows", 100)
293
+ else:
294
+ query = build_query(args)
295
+ api_key = resolve_api_key(args) if args.direct else None
296
+ url = build_title_url(args, query, "REDACTED" if args.dry_run and args.direct else api_key)
297
+ if args.dry_run:
298
+ print(json.dumps({"operation": "title", "url": url, "query": query}, ensure_ascii=False, indent=2))
299
+ return 0
300
+ payload = http_get_direct_xml(url, args.timeout) if args.direct else http_get_json(url, args.timeout, via_proxy=True)
301
+ print(json.dumps(payload, ensure_ascii=False, indent=2) if args.json else format_text(payload))
302
+ return 0
303
+ except HelperError as error:
304
+ print(str(error), file=sys.stderr)
305
+ return 1
306
+
307
+
308
+ if __name__ == "__main__":
309
+ raise SystemExit(run())
@@ -0,0 +1,32 @@
1
+ """XML normalization for the building-register direct API."""
2
+
3
+ import xml.etree.ElementTree as ET
4
+ from typing import Any, Dict, Type
5
+
6
+
7
+ def parse_direct_xml(raw: bytes, error_type: Type[RuntimeError]) -> Dict[str, Any]:
8
+ try:
9
+ root = ET.fromstring(raw)
10
+ except ET.ParseError as error:
11
+ raise error_type("건축물대장 API 응답 XML이 올바르지 않습니다.") from error
12
+ result_code = (root.findtext("./header/resultCode") or "").strip()
13
+ result_message = (root.findtext("./header/resultMsg") or "").strip()
14
+ if result_code not in {"", "0", "00"}:
15
+ raise error_type(f"건축물대장 API 오류: {result_message or result_code}")
16
+ body = root.find("./body")
17
+ if body is None:
18
+ raise error_type("건축물대장 API 응답 본문이 없습니다.")
19
+ items = [{child.tag: child.text or "" for child in item} for item in body.findall("./items/item")]
20
+ pagination = {}
21
+ for field, default in (("pageNo", 1), ("numOfRows", len(items)), ("totalCount", len(items))):
22
+ try:
23
+ pagination[field] = int(body.findtext(field) or default)
24
+ except ValueError as error:
25
+ raise error_type(f"건축물대장 API 응답 {field}가 올바른 정수가 아닙니다.") from error
26
+ if pagination[field] < 0:
27
+ raise error_type(f"건축물대장 API 응답 {field}가 음수입니다.")
28
+ return {
29
+ "page": pagination["pageNo"], "page_size": pagination["numOfRows"],
30
+ "total_count": pagination["totalCount"], "items": items,
31
+ "source": {"data_go_kr_dataset": "15134735", "operation": "getBrTitleInfo", "response_format": "XML"},
32
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "building-register-search",
3
+ "description": "Use when the user asks to look up a Korean building-register title record by address, PNU, legal-dong code, or parcel number.",
4
+ "profiles": [
5
+ "vault",
6
+ "lookup"
7
+ ],
8
+ "frontmatter": "name: building-register-search\ndescription: Use when the user asks to look up a Korean building-register title record by address, PNU, legal-dong code, or parcel number.\nlicense: MIT\nmetadata:\n category: real-estate\n locale: ko-KR\n phase: v1"
9
+ }
@@ -0,0 +1,154 @@
1
+ # Bunjang Search
2
+
3
+ ## What this skill does
4
+
5
+ upstream [`bunjang-cli`](https://www.npmjs.com/package/bunjang-cli) / [`pinion05/bunjangcli`](https://github.com/pinion05/bunjangcli) 를 사용해 번개장터에서 아래 흐름을 처리한다.
6
+
7
+ - 상품 검색
8
+ - 상품 상세조회
9
+ - 선택적 찜/채팅
10
+ - 다페이지 대량 수집
11
+ - AI 분석용 TOON chunk export
12
+
13
+ ## Core policy
14
+
15
+ - 기본 경로는 **항상 CLI first** 다.
16
+ - 기본 명령은 `npx --yes bunjang-cli ...` 형식을 쓴다.
17
+ - `auth login` 은 headful 브라우저 + **TTY / interactive 터미널**이 필요하다.
18
+ - 로그인 전에는 검색/상세조회/대량 수집 위주로 답하고, `favorite` / `chat` / `purchase` 는 **선택적 로그인 플로우**로만 안내한다.
19
+ - 대량 수집은 `--start-page`, `--pages`, `--max-items`, `--with-detail`, `--output` 조합을 우선 쓴다.
20
+ - AI 분석용 export 는 `--ai --output <directory>` 로 `.toon` chunk 를 만든다.
21
+ - 찜/채팅은 명시적으로 요청받지 않으면 실행하지 않는다.
22
+
23
+ ## When to use
24
+
25
+ - "번개장터에서 아이폰 검색해줘"
26
+ - "번장에서 이 상품 상세 봐줘"
27
+ - "여러 페이지 모아서 JSON으로 저장해줘"
28
+ - "AI 평가용으로 번개장터 결과를 chunk 로 만들어줘"
29
+
30
+ ## When not to use
31
+
32
+ - 계정 로그인 없이 바로 찜/채팅을 강행해야 하는 경우
33
+ - 구매 확정/결제 자동화를 기대하는 경우
34
+ - 번개장터 외 다른 중고거래 플랫폼을 동시에 다뤄야 하는 경우
35
+
36
+ ## Quick smoke test
37
+
38
+ ```bash
39
+ npx --yes bunjang-cli --help
40
+ npx --yes bunjang-cli --json auth status
41
+ npx --yes bunjang-cli --json search "아이폰" --max-items 3 --sort date
42
+ npx --yes bunjang-cli --json item get 354957625
43
+ ```
44
+
45
+ ## Login flow
46
+
47
+ ```bash
48
+ npx --yes bunjang-cli auth login
49
+ npx --yes bunjang-cli auth logout
50
+ npx --yes bunjang-cli --json auth status
51
+ ```
52
+
53
+ - `auth login` 은 브라우저에서 로그인한 뒤 **터미널로 돌아와 Enter 를 눌러야** 완료된다.
54
+ - 그래서 비-TTY 실행 대신 interactive 세션에서만 진행한다.
55
+
56
+ ## Search flow
57
+
58
+ ```bash
59
+ npx --yes bunjang-cli search "아이폰"
60
+ npx --yes bunjang-cli search "아이폰" --price-min 500000 --price-max 1200000
61
+ npx --yes bunjang-cli search "아이폰" --sort date
62
+ npx --yes bunjang-cli --json search "아이폰" --max-items 5
63
+ ```
64
+
65
+ 검색 결과는 광고/매입글/악세서리 노이즈가 섞이고, search summary 의 `location` 이 noisy 하거나 `description` / `status` 가 비어 있을 수 있다. 그래서 **검색 단계는 제목/가격 중심 1차 triage** 로만 쓴다.
66
+
67
+ - 기기명/용량 키워드 일치 여부
68
+ - 가격대 범위
69
+ - 판매 링크/썸네일 중복 여부
70
+
71
+ `description`, `status`, 깔끔한 `location` 이 필요하면 **반드시 `item get` 또는 `--with-detail` 이후** 에만 판단한다.
72
+
73
+ ## Detail flow
74
+
75
+ ```bash
76
+ npx --yes bunjang-cli item get 354957625
77
+ npx --yes bunjang-cli --json item get 354957625
78
+ npx --yes bunjang-cli --json item list --ids 354957625,354801707
79
+ ```
80
+
81
+ 상세조회에서는 아래 필드를 먼저 읽는다.
82
+
83
+ - `price`
84
+ - `description`
85
+ - `location`
86
+ - `category`
87
+ - `status`
88
+ - `sellerName`
89
+ - `sellerItemCount`
90
+ - `sellerFollowerCount`
91
+ - `sellerReviewCount`
92
+ - `favoriteCount`
93
+ - `transportUsed`
94
+
95
+ ## Bulk collection
96
+
97
+ ```bash
98
+ npx --yes bunjang-cli search "아이폰" \
99
+ --start-page 1 \
100
+ --pages 5 \
101
+ --max-items 50 \
102
+ --sort date \
103
+ --with-detail \
104
+ --output artifacts/bunjang-iphone.json
105
+ ```
106
+
107
+ 검증할 때는 export 파일 생성 여부와 top-level `items[]` 안의 `summary` / `detail` / optional `error` 구조, 그리고 각 item 의 `sourcePage` 또는 `summary.raw.page` 를 같이 확인한다.
108
+
109
+ ## AI export
110
+
111
+ ```bash
112
+ npx --yes bunjang-cli search "아이폰" \
113
+ --start-page 1 \
114
+ --pages 5 \
115
+ --max-items 50 \
116
+ --with-detail \
117
+ --ai \
118
+ --output artifacts/bunjang-iphone-ai
119
+ ```
120
+
121
+ - `--ai` 에서는 `--output` 이 **파일이 아니라 디렉토리** 여야 한다.
122
+ - 결과는 `items-1.toon` 형태 chunk 로 저장된다.
123
+ - AI 평가용으로 여러 서브에이전트에 분산 읽기시키기 좋다.
124
+
125
+ ## Optional favorite/chat flow
126
+
127
+ 로그인된 interactive 세션에서만 아래 액션을 진행한다.
128
+
129
+ ```bash
130
+ npx --yes bunjang-cli --json favorite list
131
+ npx --yes bunjang-cli --json favorite add 354957625
132
+ npx --yes bunjang-cli --json favorite remove 354957625
133
+ npx --yes bunjang-cli --json chat list
134
+ npx --yes bunjang-cli --json chat start 354957625 --message "안녕하세요"
135
+ npx --yes bunjang-cli --json chat send 84191651 --message "상품 상태 괜찮을까요?"
136
+ ```
137
+
138
+ - 찜/채팅은 **로그인이 필요한 선택적 기능**이다.
139
+ - 검증 목적이면 `favorite list` 로 세션을 먼저 확인하고, 같은 상품에 대해 `favorite add` / `favorite remove` 를 왕복 실행한다.
140
+ - `chat start` 는 상품 페이지에서 새 대화를 열 때, `chat send` 는 기존 thread 에 메시지를 보낼 때 쓴다.
141
+
142
+ ## Recommended response format
143
+
144
+ 1. 검색어가 넓으면 예산/모델/지역을 먼저 좁힌다.
145
+ 2. 검색 결과 상위 3~5개는 제목/가격 중심 1차 요약만 한다.
146
+ 3. `description` / `status` / `location` 판단이 필요하면 `item get` 또는 `--with-detail` 로 상세를 먼저 읽는다.
147
+ 4. 로그인 액션이 필요하면 "지금은 로그인 세션이 없으니 interactive TTY 에서 `auth login` 후 다시 진행" 이라고 분명히 말한다.
148
+ 5. 대량 분석이면 JSON export 또는 TOON chunk 생성 경로를 제안한다.
149
+
150
+ ## Done when
151
+
152
+ - 검색/상세조회/대량 수집/AI export 중 필요한 경로가 안내되었다.
153
+ - 찜/채팅은 로그인 필요성과 선택적 성격이 명확히 고지되었다.
154
+ - 자동 구매/결제는 범위 밖이라고 분명히 말했다.
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "bunjang-search",
3
+ "description": "번개장터 검색, 상세조회, 찜, 채팅, 대량 수집, AI TOON export를 bunjang-cli로 안내한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
4
+ "profiles": [
5
+ "vault",
6
+ "browser",
7
+ "action:commerce"
8
+ ],
9
+ "frontmatter": "name: bunjang-search\ndescription: 번개장터 검색, 상세조회, 찜, 채팅, 대량 수집, AI TOON export를 bunjang-cli로 안내한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: marketplace\n locale: ko-KR\n phase: v1"
10
+ }