mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
Make intercity timetable lookup follow Tmoney form contract
Add a read-only timetable helper that starts a cookie-backed Tmoney session, submits the hidden browser fields required by readAlcnList.do, and parses readSasFeeInf schedule rows into JSON. Constraint: Tmoney returns a generic errorCont page unless bef_Aft_Dvs and req_Rec_Num are posted with the timetable form. Rejected: Browser automation-first lookup | official HTTP flow works when the browser-submitted hidden fields are included. Confidence: high Scope-risk: narrow Directive: Do not automate final card submission or payment from this skill without explicit user confirmation. Tested: python3 -m py_compile intercity-bus-booking/scripts/intercity_bus_search.py; python3 intercity-bus-booking/scripts/intercity_bus_search.py --depart-code 0511601 --arrive-code 2482701 --depart-name 동서울 --arrive-name 속초 --date 20260520 --limit 1; rsync to ~/.claude/skills and ~/.agents/skills; ./scripts/validate-skills.sh; node --test scripts/skill-docs.test.js; npm run lint Not-tested: temporary seat hold, cancellation, card entry, and payment flows
This commit is contained in:
parent
dd11fa9d30
commit
b4aae5b295
5 changed files with 272 additions and 8 deletions
|
|
@ -24,15 +24,29 @@
|
|||
## 기본 흐름
|
||||
|
||||
1. 쿠키 jar를 만들고 티머니 시외버스 페이지를 열어 세션을 시작한다.
|
||||
2. `POST /otck/readAlcnList.do` 로 배차를 조회한다.
|
||||
2. `POST /otck/readAlcnList.do` 로 배차를 조회한다. 이때 브라우저 JS가 붙이는 `bef_Aft_Dvs=D`, `req_Rec_Num=10`을 반드시 같이 보낸다.
|
||||
3. 결과의 `readSasFeeInf(...)` 인자를 파싱해 후보를 정리한다.
|
||||
4. 선택 후보는 `POST /otck/readSatsFee.do` 로 좌석/요금 단계 진입을 확인한다.
|
||||
5. 사용자가 원하면 `POST /otck/readPcpySats.do` 로 공식 카드정보 입력 페이지에 진입하도록 handoff한다.
|
||||
6. 뒤로가기/취소성 이동으로 좌석 선택 단계에 복귀해 임시 선점을 해제할 수 있는지 확인한다.
|
||||
|
||||
## read-only 조회 helper
|
||||
|
||||
```bash
|
||||
python3 intercity-bus-booking/scripts/intercity_bus_search.py \
|
||||
--depart-code 0511601 \
|
||||
--arrive-code 2482701 \
|
||||
--depart-name 동서울 \
|
||||
--arrive-name 속초 \
|
||||
--date 20260520
|
||||
```
|
||||
|
||||
이 helper는 쿠키 세션을 시작하고 공식 배차 조회 POST를 수행한 뒤 출발시각, 운수사, 등급, 요금, 잔여/총 좌석을 JSON으로 출력한다. 임시 좌석 선점이나 결제 단계는 수행하지 않는다.
|
||||
|
||||
## 주의할 점
|
||||
|
||||
- 결제 자동화는 포함하지 않는다. 공식 페이지의 결제 직전 단계까지 보조하는 assisted checkout 흐름이다.
|
||||
- 티머니 시외버스 터미널 코드는 KOBUS 고속버스 코드와 다르므로 혼용하지 않는다.
|
||||
- 일부 표면은 `txbus` 계열 URL과 연결될 수 있지만, 검증된 기본 URL은 `intercitybus.tmoney.co.kr` 이다.
|
||||
- stateless POST보다 쿠키와 referer를 유지하는 흐름이 안정적이다.
|
||||
- `bef_Aft_Dvs` 또는 `req_Rec_Num`을 누락하면 실제 배차가 있어도 `errorCont`가 포함된 일반 오류 페이지가 반환될 수 있다.
|
||||
|
|
|
|||
|
|
@ -68,8 +68,12 @@ ic=0
|
|||
iv=0
|
||||
depr_Dt=YYYYMMDD
|
||||
depr_Time=000000
|
||||
bef_Aft_Dvs=D
|
||||
req_Rec_Num=10
|
||||
```
|
||||
|
||||
`bef_Aft_Dvs` and `req_Rec_Num` are required hidden fields from the browser JavaScript `readAlcnListEntry(...)`. If they are omitted, Tmoney can return a generic error page with no schedules.
|
||||
|
||||
Parse schedule buttons/rows. The next-stage parameters are often embedded in `readSasFeeInf(...)` onclick arguments.
|
||||
|
||||
### 3. Enter Fare / Seat-Count Stage
|
||||
|
|
@ -111,6 +115,21 @@ rtrp_Depr_Dt
|
|||
|
||||
A successful response lands on the official `카드정보 입력` page and includes a temporary seat hold identifier such as `sats_Pcpy_Id`.
|
||||
|
||||
## Timetable Helper
|
||||
|
||||
For read-only timetable lookup, use the bundled helper before attempting browser automation:
|
||||
|
||||
```bash
|
||||
python3 intercity-bus-booking/scripts/intercity_bus_search.py \
|
||||
--depart-code 0511601 \
|
||||
--arrive-code 2482701 \
|
||||
--depart-name 동서울 \
|
||||
--arrive-name 속초 \
|
||||
--date 20260520
|
||||
```
|
||||
|
||||
The helper starts a cookie-backed session, posts the browser-required timetable fields, parses `readSasFeeInf(...)`, and prints JSON with departure time, company, class, fares, and remaining/total seats. It intentionally does not create temporary holds or submit payment data.
|
||||
|
||||
## Checkout-Entry Link Helper
|
||||
|
||||
A helper-served HTML page can auto-submit a POST form directly to:
|
||||
|
|
@ -145,13 +164,15 @@ When a checkout-entry helper is created, say that it opens the official Tmoney c
|
|||
|
||||
1. **Mixing terminal code systems.** Tmoney 시외버스 codes are not KOBUS codes.
|
||||
2. **Assuming checkout-entry equals final payment.** `readPcpySats.do` can open the card-information page, but final payment remains a separate manual step.
|
||||
3. **Replaying stale hold payloads.** A repeated POST for the same route/seat can fail or create confusing results. Generate a fresh seat-stage payload for real use.
|
||||
4. **Skipping cancellation/back flow.** Use the official cancellation/back form (`pcpyCanc=C` via `readSatsFee.do` when available) for abandoned holds.
|
||||
5. **Overusing browser automation.** Use browser only for endpoint discovery or visual verification after HTTP probing.
|
||||
3. **Omitting hidden timetable fields.** `readAlcnList.do` needs `bef_Aft_Dvs=D` and `req_Rec_Num=10`; without them it may return a generic `errorCont` page of about 13 KB instead of schedule rows.
|
||||
4. **Replaying stale hold payloads.** A repeated POST for the same route/seat can fail or create confusing results. Generate a fresh seat-stage payload for real use.
|
||||
5. **Skipping cancellation/back flow.** Use the official cancellation/back form (`pcpyCanc=C` via `readSatsFee.do` when available) for abandoned holds.
|
||||
6. **Overusing browser automation.** Use browser only for endpoint discovery or visual verification after HTTP probing.
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Route/terminal codes were resolved from Tmoney 시외버스, not guessed or copied from KOBUS.
|
||||
- [ ] Timetable POST included `bef_Aft_Dvs=D` and `req_Rec_Num=10`.
|
||||
- [ ] Timetable response was parsed for schedule rows/buttons and next-stage parameters.
|
||||
- [ ] Fare/seat-stage response contains `form#readPcpySats` and expected hidden fields.
|
||||
- [ ] Checkout-entry response contains `카드정보 입력` and a hold identifier such as `sats_Pcpy_Id` before reporting success.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Tmoney 시외버스 HTTP/API Probe Notes
|
||||
|
||||
Session-proven on 2026-05-08. Goal: avoid browser automation where possible.
|
||||
Session-proven on 2026-05-08 and re-verified on 2026-05-13. Goal: avoid browser automation where possible.
|
||||
|
||||
## Base
|
||||
|
||||
|
|
@ -24,10 +24,11 @@ Example tested route/date:
|
|||
동서울(0511601) -> 속초(2482701), 2026-05-09
|
||||
```
|
||||
|
||||
Observed result:
|
||||
Observed results:
|
||||
|
||||
```text
|
||||
14 reservation buttons/schedules
|
||||
2026-05-09: 14 reservation buttons/schedules
|
||||
2026-05-20: 20 readSasFeeInf schedule buttons, first departure 06:05 금강고속 우등, 24/28 seats
|
||||
```
|
||||
|
||||
Typical POST fields:
|
||||
|
|
@ -43,8 +44,12 @@ ic=0
|
|||
iv=0
|
||||
depr_Dt=YYYYMMDD
|
||||
depr_Time=000000
|
||||
bef_Aft_Dvs=D
|
||||
req_Rec_Num=10
|
||||
```
|
||||
|
||||
`bef_Aft_Dvs=D` and `req_Rec_Num=10` are not optional. They are appended by the site JavaScript (`readAlcnListEntry(bef_Aft_Dvs, req_Rec_Num)`) before the browser submits `#onewayInfo`. Omitting them returned a generic error page (`errorCont`, about 13,770 bytes) with no `readSasFeeInf(...)` schedules in live probing.
|
||||
|
||||
The next-stage values are embedded in `readSasFeeInf(...)` onclick calls. Example prefix:
|
||||
|
||||
```text
|
||||
|
|
@ -113,5 +118,6 @@ A POST back to `/otck/readSatsFee.do` with `pcpyCanc=C` and the hold fields retu
|
|||
|
||||
- Login was not required for timetable lookup, fare/seat-stage entry, or card-information page entry in the tested flow.
|
||||
- CAPTCHA was not observed in the tested flow.
|
||||
- A generic `errorCont` response usually means the posted form contract is incomplete, not necessarily that the route is unavailable; first verify `bef_Aft_Dvs` and `req_Rec_Num`.
|
||||
- Payment/card-info submission is separate and should not be automated without explicit confirmation.
|
||||
- Terminal codes are Tmoney-specific and must not be mixed with KOBUS codes.
|
||||
|
|
|
|||
223
intercity-bus-booking/scripts/intercity_bus_search.py
Executable file
223
intercity-bus-booking/scripts/intercity_bus_search.py
Executable file
|
|
@ -0,0 +1,223 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Search Tmoney intercity-bus timetables through the official read-only flow.
|
||||
|
||||
This helper intentionally stops at timetable parsing. It does not create seat holds,
|
||||
submit card data, or perform payment.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import html
|
||||
import http.cookiejar
|
||||
import json
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Iterable
|
||||
|
||||
BASE_URL = "https://intercitybus.tmoney.co.kr"
|
||||
ENTRY_PATH = "/otck/trmlInfEnty.do"
|
||||
TIMETABLE_PATH = "/otck/readAlcnList.do"
|
||||
DEFAULT_UA = (
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125 Safari/537.36"
|
||||
)
|
||||
|
||||
ROW_RE = re.compile(r"<tr>\s*(.*?)readSasFeeInf\((.*?)\).*?</tr>", re.DOTALL | re.IGNORECASE)
|
||||
TD_WRAP_RE = re.compile(r'<div class="td_wrap1">(.*?)</div>', re.DOTALL | re.IGNORECASE)
|
||||
TAG_RE = re.compile(r"<[^>]+>")
|
||||
ARG_RE = re.compile(r"'((?:\\'|[^'])*)'")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Schedule:
|
||||
departure_time: str | None
|
||||
company: str | None
|
||||
duration: str | None
|
||||
bus_class: str | None
|
||||
adult_fare: str | None
|
||||
child_fare: str | None
|
||||
student_fare: str | None
|
||||
remaining_seats: int | None
|
||||
total_seats: int | None
|
||||
raw_args: list[str]
|
||||
|
||||
|
||||
def _ssl_context() -> ssl.SSLContext:
|
||||
# Tmoney has historically required curl -k in probes on some machines.
|
||||
# Keep this helper resilient while limiting it to the official host.
|
||||
return ssl._create_unverified_context() # noqa: SLF001
|
||||
|
||||
|
||||
def _strip(value: str) -> str:
|
||||
value = re.sub(r"<!--.*?-->", "", value, flags=re.DOTALL)
|
||||
value = TAG_RE.sub("", value)
|
||||
return html.unescape(value).replace("\xa0", " ").strip()
|
||||
|
||||
|
||||
def _open(opener: urllib.request.OpenerDirector, request: urllib.request.Request, timeout: int) -> str:
|
||||
# urllib opener.open does not accept context; HTTPS context must be installed in handler.
|
||||
with opener.open(request, timeout=timeout) as response:
|
||||
charset = response.headers.get_content_charset() or "utf-8"
|
||||
return response.read().decode(charset, errors="replace")
|
||||
|
||||
|
||||
def build_opener() -> urllib.request.OpenerDirector:
|
||||
jar = http.cookiejar.CookieJar()
|
||||
return urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(jar),
|
||||
urllib.request.HTTPSHandler(context=_ssl_context()),
|
||||
)
|
||||
|
||||
|
||||
def search_timetable(
|
||||
depart_code: str,
|
||||
arrive_code: str,
|
||||
depart_name: str,
|
||||
arrive_name: str,
|
||||
date: str,
|
||||
time: str = "000000",
|
||||
adults: int = 1,
|
||||
students: int = 0,
|
||||
children: int = 0,
|
||||
veterans: int = 0,
|
||||
timeout: int = 20,
|
||||
) -> tuple[str, list[Schedule]]:
|
||||
opener = build_opener()
|
||||
entry_req = urllib.request.Request(
|
||||
f"{BASE_URL}{ENTRY_PATH}",
|
||||
headers={"User-Agent": DEFAULT_UA},
|
||||
method="GET",
|
||||
)
|
||||
_open(opener, entry_req, timeout)
|
||||
|
||||
fields = {
|
||||
"depr_Trml_Cd": depart_code,
|
||||
"arvl_Trml_Cd": arrive_code,
|
||||
"depr_Trml_Nm": depart_name,
|
||||
"arvl_Trml_Nm": arrive_name,
|
||||
"ig": str(adults),
|
||||
"im": str(students),
|
||||
"ic": str(children),
|
||||
"iv": str(veterans),
|
||||
"depr_Dt": date,
|
||||
"depr_Time": time,
|
||||
# Required by the browser JS readAlcnListEntry(). Missing either field
|
||||
# returns a generic error page with no schedule rows.
|
||||
"bef_Aft_Dvs": "D",
|
||||
"req_Rec_Num": "10",
|
||||
}
|
||||
req = _post_opener_request(f"{BASE_URL}{TIMETABLE_PATH}", fields)
|
||||
body = _open(opener, req, timeout)
|
||||
return body, parse_schedules(body)
|
||||
|
||||
|
||||
def _post_opener_request(url: str, data: dict[str, str]) -> urllib.request.Request:
|
||||
encoded = urllib.parse.urlencode(data).encode("utf-8")
|
||||
return urllib.request.Request(
|
||||
url,
|
||||
data=encoded,
|
||||
headers={
|
||||
"User-Agent": DEFAULT_UA,
|
||||
"Referer": f"{BASE_URL}{ENTRY_PATH}",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
|
||||
def parse_schedules(body: str) -> list[Schedule]:
|
||||
schedules: list[Schedule] = []
|
||||
for row_html, arg_text in ROW_RE.findall(body):
|
||||
args = [a.replace("\\'", "'") for a in ARG_RE.findall(arg_text)]
|
||||
cells = [_strip(x) for x in TD_WRAP_RE.findall(row_html)]
|
||||
departure = cells[0] if len(cells) > 0 else (args[8][:2] + ":" + args[8][2:4] if len(args) > 8 else None)
|
||||
company_cell = cells[1] if len(cells) > 1 else None
|
||||
company = args[11] if len(args) > 11 else None
|
||||
duration = None
|
||||
if company_cell and company and company_cell.startswith(company):
|
||||
duration = company_cell[len(company):].strip() or None
|
||||
elif company_cell:
|
||||
duration = company_cell
|
||||
bus_class = args[12] if len(args) > 12 else (cells[2] if len(cells) > 2 else None)
|
||||
remaining = int(args[16]) if len(args) > 16 and args[16].isdigit() else None
|
||||
total = int(args[17]) if len(args) > 17 and args[17].isdigit() else None
|
||||
schedules.append(
|
||||
Schedule(
|
||||
departure_time=departure,
|
||||
company=company,
|
||||
duration=duration,
|
||||
bus_class=bus_class,
|
||||
adult_fare=cells[3] if len(cells) > 3 else None,
|
||||
child_fare=cells[4] if len(cells) > 4 else None,
|
||||
student_fare=cells[5] if len(cells) > 5 else None,
|
||||
remaining_seats=remaining,
|
||||
total_seats=total,
|
||||
raw_args=args,
|
||||
)
|
||||
)
|
||||
return schedules
|
||||
|
||||
|
||||
def main(argv: Iterable[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Search Tmoney intercity-bus timetable")
|
||||
parser.add_argument("--depart-code", required=True)
|
||||
parser.add_argument("--arrive-code", required=True)
|
||||
parser.add_argument("--depart-name", required=True)
|
||||
parser.add_argument("--arrive-name", required=True)
|
||||
parser.add_argument("--date", required=True, help="YYYYMMDD")
|
||||
parser.add_argument("--time", default="000000", help="HHMMSS, default 000000")
|
||||
parser.add_argument("--adults", type=int, default=1)
|
||||
parser.add_argument("--students", type=int, default=0)
|
||||
parser.add_argument("--children", type=int, default=0)
|
||||
parser.add_argument("--veterans", type=int, default=0)
|
||||
parser.add_argument("--timeout", type=int, default=20)
|
||||
parser.add_argument("--limit", type=int, default=20)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if not re.fullmatch(r"\d{8}", args.date):
|
||||
parser.error("--date must be YYYYMMDD")
|
||||
if not re.fullmatch(r"\d{6}", args.time):
|
||||
parser.error("--time must be HHMMSS")
|
||||
|
||||
body, schedules = search_timetable(
|
||||
depart_code=args.depart_code,
|
||||
arrive_code=args.arrive_code,
|
||||
depart_name=args.depart_name,
|
||||
arrive_name=args.arrive_name,
|
||||
date=args.date,
|
||||
time=args.time,
|
||||
adults=args.adults,
|
||||
students=args.students,
|
||||
children=args.children,
|
||||
veterans=args.veterans,
|
||||
timeout=args.timeout,
|
||||
)
|
||||
result = {
|
||||
"route": {
|
||||
"depart_code": args.depart_code,
|
||||
"arrive_code": args.arrive_code,
|
||||
"depart_name": args.depart_name,
|
||||
"arrive_name": args.arrive_name,
|
||||
"date": args.date,
|
||||
"time": args.time,
|
||||
},
|
||||
"count": len(schedules),
|
||||
"items": [asdict(s) for s in schedules[: args.limit]],
|
||||
"failure_mode": None,
|
||||
}
|
||||
if not schedules:
|
||||
result["failure_mode"] = (
|
||||
"No readSasFeeInf schedule rows found. Check terminal codes/date, sold-out/no-service state, "
|
||||
"or whether Tmoney returned its generic error page."
|
||||
)
|
||||
result["error_page_marker_count"] = body.count("errorCont")
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if schedules else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
"scripts": {
|
||||
"build": "npm run build --workspaces --if-present",
|
||||
"build:manus-bundle": "node scripts/build-manus-bundle.js",
|
||||
"lint": "node --check scripts/skill-docs.test.js scripts/korean_character_count.js scripts/test_korean_character_count.js scripts/build-manus-bundle.js scripts/test_build_manus_bundle.js && python3 -m py_compile scripts/k_skill_cleaner.py scripts/test_k_skill_cleaner.py corporate-registration-consulting/scripts/fill_official_hwp.py k-skill-cleaner/scripts/k_skill_cleaner.py scripts/fine_dust.py scripts/test_fine_dust.py scripts/ktx_booking.py scripts/test_ktx_booking.py scripts/sillok_search.py scripts/test_sillok_search.py scripts/korean_spell_check.py scripts/test_korean_spell_check.py scripts/patent_search.py scripts/test_patent_search.py scripts/mfds_drug_safety.py scripts/test_mfds_drug_safety.py scripts/mfds_food_safety.py scripts/test_mfds_food_safety.py scripts/zipcode_search.py scripts/test_zipcode_search.py scripts/subway_lost_property.py scripts/test_subway_lost_property.py scripts/geeknews_search.py scripts/test_geeknews_search.py scripts/test_naver_blog_search.py scripts/test_korean_slang_writing.py scripts/kakaotalk_mac.py scripts/test_kakaotalk_mac.py scripts/test_coupang_partners_mcp_wrapper.py coupang-product-search/scripts/coupang_partners_mcp.py kakaotalk-mac/scripts/kakaotalk_mac.py naver-blog-research/scripts/_naver_http.py naver-blog-research/scripts/naver_search.py naver-blog-research/scripts/naver_read.py naver-blog-research/scripts/naver_download_images.py korean-slang-writing/scripts/_slang_http.py korean-slang-writing/scripts/slang_search.py korean-slang-writing/scripts/slang_lookup.py korean-scholarship-search/scripts/scholarship_filter.py korean-scholarship-search/scripts/test_scholarship_filter.py korean-scholarship-search/scripts/university_search_plan.py danawa-price-search/scripts/danawa_search.py kosis-stats/scripts/run_kosis_stats.py kosis-stats/tests/test_run_kosis_stats.py && npm run lint --workspaces --if-present && ./scripts/validate-skills.sh",
|
||||
"lint": "node --check scripts/skill-docs.test.js scripts/korean_character_count.js scripts/test_korean_character_count.js scripts/build-manus-bundle.js scripts/test_build_manus_bundle.js && python3 -m py_compile scripts/k_skill_cleaner.py scripts/test_k_skill_cleaner.py corporate-registration-consulting/scripts/fill_official_hwp.py k-skill-cleaner/scripts/k_skill_cleaner.py scripts/fine_dust.py scripts/test_fine_dust.py scripts/ktx_booking.py scripts/test_ktx_booking.py scripts/sillok_search.py scripts/test_sillok_search.py scripts/korean_spell_check.py scripts/test_korean_spell_check.py scripts/patent_search.py scripts/test_patent_search.py scripts/mfds_drug_safety.py scripts/test_mfds_drug_safety.py scripts/mfds_food_safety.py scripts/test_mfds_food_safety.py scripts/zipcode_search.py scripts/test_zipcode_search.py scripts/subway_lost_property.py scripts/test_subway_lost_property.py scripts/geeknews_search.py scripts/test_geeknews_search.py scripts/test_naver_blog_search.py scripts/test_korean_slang_writing.py scripts/kakaotalk_mac.py scripts/test_kakaotalk_mac.py scripts/test_coupang_partners_mcp_wrapper.py coupang-product-search/scripts/coupang_partners_mcp.py kakaotalk-mac/scripts/kakaotalk_mac.py naver-blog-research/scripts/_naver_http.py naver-blog-research/scripts/naver_search.py naver-blog-research/scripts/naver_read.py naver-blog-research/scripts/naver_download_images.py korean-slang-writing/scripts/_slang_http.py korean-slang-writing/scripts/slang_search.py korean-slang-writing/scripts/slang_lookup.py korean-scholarship-search/scripts/scholarship_filter.py korean-scholarship-search/scripts/test_scholarship_filter.py korean-scholarship-search/scripts/university_search_plan.py danawa-price-search/scripts/danawa_search.py kosis-stats/scripts/run_kosis_stats.py kosis-stats/tests/test_run_kosis_stats.py intercity-bus-booking/scripts/intercity_bus_search.py && npm run lint --workspaces --if-present && ./scripts/validate-skills.sh",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js scripts/test_build_manus_bundle.js && PYTHONPATH=.:scripts python3 -m unittest scripts.test_k_skill_cleaner scripts.test_fine_dust scripts.test_ktx_booking scripts.test_sillok_search scripts.test_korean_spell_check scripts.test_patent_search scripts.test_mfds_drug_safety scripts.test_mfds_food_safety scripts.test_zipcode_search scripts.test_subway_lost_property scripts.test_geeknews_search scripts.test_naver_blog_search scripts.test_korean_slang_writing scripts.test_kakaotalk_mac scripts.test_coupang_partners_mcp_wrapper && PYTHONPATH=.:scripts:korean-scholarship-search/scripts python3 -m unittest discover -s korean-scholarship-search/scripts -p 'test_scholarship_filter.py' && PYTHONPATH=.:scripts:kosis-stats/scripts python3 -m unittest discover -s kosis-stats/tests -p 'test_run_kosis_stats.py' && npm run test --workspaces --if-present && ./scripts/validate-skills.sh",
|
||||
"pack:dry-run": "npm pack --workspace k-lotto --dry-run && npm pack --workspace daiso-product-search --dry-run && npm pack --workspace market-kurly-search --dry-run && npm pack --workspace blue-ribbon-nearby --dry-run && npm pack --workspace kakao-bar-nearby --dry-run && npm pack --workspace cheap-gas-nearby --dry-run && npm pack --workspace public-restroom-nearby --dry-run && npm pack --workspace parking-lot-search --dry-run && npm pack --workspace court-auction-notice-search --dry-run && npm pack --workspace donation-place-search --dry-run && npm pack --workspace gongsijiga-search --dry-run && npm pack --workspace kbl-results --dry-run && npm pack --workspace kleague-results --dry-run && npm pack --workspace lck-analytics --dry-run && npm pack --workspace toss-securities --dry-run && npm pack --workspace hipass-receipt --dry-run && npm pack --workspace used-car-price-search --dry-run && npm pack --workspace k-skill-rhwp --dry-run && npm pack --workspace korean-marathon-schedule --dry-run",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue