@nomadamas/k-skill 0.2.5 → 0.4.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 (77) hide show
  1. package/package.json +1 -1
  2. package/skills/bok-ecos-stats/instruction.md +1 -1
  3. package/skills/bunjang-search/references/DISCLAIMER.md +23 -0
  4. package/skills/coupang-product-search/references/DISCLAIMER.md +23 -0
  5. package/skills/daangn-cars-search/references/DISCLAIMER.md +23 -0
  6. package/skills/daangn-jobs-search/references/DISCLAIMER.md +23 -0
  7. package/skills/daangn-realty-search/references/DISCLAIMER.md +23 -0
  8. package/skills/daangn-used-goods-search/references/DISCLAIMER.md +23 -0
  9. package/skills/daishin-report-search/references/DISCLAIMER.md +23 -0
  10. package/skills/daiso-product-search/references/DISCLAIMER.md +23 -0
  11. package/skills/danawa-price-search/references/DISCLAIMER.md +23 -0
  12. package/skills/delivery-tracking/references/DISCLAIMER.md +23 -0
  13. package/skills/express-bus-booking/references/DISCLAIMER.md +23 -0
  14. package/skills/fine-dust-location/skill.json +1 -7
  15. package/skills/flight-ticket-search/references/DISCLAIMER.md +23 -0
  16. package/skills/foresttrip-vacancy/references/DISCLAIMER.md +23 -0
  17. package/skills/intercity-bus-booking/references/DISCLAIMER.md +23 -0
  18. package/skills/job-posting-match/references/DISCLAIMER.md +23 -0
  19. package/skills/jobkorea-talent-search/references/DISCLAIMER.md +23 -0
  20. package/skills/k-skill-setup/skill.json +1 -7
  21. package/skills/kakao-bar-nearby/references/DISCLAIMER.md +15 -0
  22. package/skills/kakao-map/references/DISCLAIMER.md +15 -0
  23. package/skills/kakaotalk-mac/references/DISCLAIMER.md +15 -0
  24. package/skills/kbl-results/references/DISCLAIMER.md +13 -0
  25. package/skills/kbo-results/references/DISCLAIMER.md +13 -0
  26. package/skills/keris-academic-search/references/DISCLAIMER.md +13 -0
  27. package/skills/keris-academic-search/references/TRADEMARK-LEGAL-STATEMENT.md +7 -0
  28. package/skills/kleague-results/references/DISCLAIMER.md +13 -0
  29. package/skills/kopis-performance-search/references/DISCLAIMER.md +13 -0
  30. package/skills/kopis-performance-search/references/TRADEMARK-LEGAL-STATEMENT.md +7 -0
  31. package/skills/kopis-performance-search/skill.json +3 -4
  32. package/skills/korean-cinema-search/references/DISCLAIMER.md +13 -0
  33. package/skills/korean-stock-search/references/DISCLAIMER.md +14 -0
  34. package/skills/korean-stock-search/references/TRADEMARK-LEGAL-STATEMENT.md +7 -0
  35. package/skills/korean-transit-route/references/DISCLAIMER.md +13 -0
  36. package/skills/ktx-booking/instruction.md +38 -221
  37. package/skills/ktx-booking/references/AUTOMATION-LEGAL-STATEMENT.md +10 -0
  38. package/skills/ktx-booking/references/DISCLAIMER.md +14 -0
  39. package/skills/ktx-booking/scripts/ktx_booking.py +176 -1274
  40. package/skills/ktx-booking/scripts/ktx_booking.py.lock +27 -0
  41. package/skills/ktx-booking/scripts/ktx_timetable.py +141 -0
  42. package/skills/ktx-booking/skill.json +3 -11
  43. package/skills/lck-analytics/references/DISCLAIMER.md +13 -0
  44. package/skills/lotto-results/references/DISCLAIMER.md +13 -0
  45. package/skills/market-kurly-search/references/DISCLAIMER.md +13 -0
  46. package/skills/myrealtrip-search/instruction.md +3 -1
  47. package/skills/myrealtrip-search/references/DISCLAIMER.md +13 -0
  48. package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +30 -8
  49. package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +62 -0
  50. package/skills/naver-ad-performance/references/DISCLAIMER.md +13 -0
  51. package/skills/naver-blog-research/references/DISCLAIMER.md +13 -0
  52. package/skills/naver-news-search/references/DISCLAIMER.md +13 -0
  53. package/skills/naver-shopping-search/references/DISCLAIMER.md +13 -0
  54. package/skills/ohou-today-deal/references/DISCLAIMER.md +13 -0
  55. package/skills/olive-young-search/references/DISCLAIMER.md +13 -0
  56. package/skills/popbill/references/DISCLAIMER.md +13 -0
  57. package/skills/saramin-talent-search/references/DISCLAIMER.md +13 -0
  58. package/skills/seoul-weather-risk/instruction.md +28 -35
  59. package/skills/seoul-weather-risk/scripts/seoul_weather_risk.py +15 -68
  60. package/skills/seoul-weather-risk/skill.json +2 -2
  61. package/skills/srt-booking/instruction.md +48 -154
  62. package/skills/srt-booking/references/AUTOMATION-LEGAL-STATEMENT.md +25 -0
  63. package/skills/srt-booking/references/DISCLAIMER.md +13 -0
  64. package/skills/srt-booking/scripts/srt_booking.py +134 -235
  65. package/skills/srt-booking/scripts/srt_booking.py.lock +207 -0
  66. package/skills/srt-booking/skill.json +3 -15
  67. package/skills/store-longevity-radar/scripts/__pycache__/store_longevity_download.cpython-312.pyc +0 -0
  68. package/skills/ticket-availability/references/DISCLAIMER.md +13 -0
  69. package/skills/{toss-securities → toss-investment}/instruction.md +1 -1
  70. package/skills/toss-investment/references/DISCLAIMER.md +14 -0
  71. package/skills/{toss-securities → toss-investment}/references/TRADEMARK-LEGAL-STATEMENT.md +1 -1
  72. package/skills/{toss-securities → toss-investment}/skill.json +2 -2
  73. package/src/assemble.js +1 -1
  74. package/src/execute.js +13 -3
  75. package/skills/lovebug-report/instruction.md +0 -185
  76. package/skills/lovebug-report/skill.json +0 -9
  77. package/skills/srt-booking/scripts/srt_seats.py +0 -156
@@ -1,272 +1,171 @@
1
- #!/usr/bin/env python3
1
+ #!/usr/bin/env -S uv run --locked --script
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = ["SRTrain==2.6.7"]
5
+ # ///
6
+ """Live, anonymous, read-only SRT timetable lookup through SRTrain."""
7
+
2
8
  from __future__ import annotations
3
9
 
4
10
  import argparse
5
- import base64
6
- import contextlib
7
- import io
8
- import importlib
9
11
  import json
10
- import os
12
+ import re
11
13
  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")
14
+ from contextlib import redirect_stdout
15
+ from datetime import date as calendar_date
16
+ from datetime import time
17
+ from typing import Any
87
18
 
19
+ from requests import RequestException
20
+ from SRT import SRT
21
+ from SRT.constants import API_ENDPOINTS, STATION_CODE
22
+ from SRT.errors import SRTError, SRTNetFunnelError
23
+ from SRT.netfunnel import NetFunnelHelper
88
24
 
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)
25
+ BOOKING_URL = "https://etk.srail.kr/hpg/hra/01/selectScheduleList.do?pageId=TK0101010000"
26
+ NETFUNNEL_URL = "https://nf.letskorail.com/ts.wseq"
94
27
 
95
28
 
96
- def train_id_payload(train: SrtTrainLike) -> dict[str, str]:
97
- return {field: getattr(train, field) for field in TRAIN_ID_FIELDS}
29
+ def build_client() -> SRT:
30
+ NetFunnelHelper.NETFUNNEL_URL = NETFUNNEL_URL
31
+ return SRT("", "", auto_login=False)
98
32
 
99
33
 
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}"
34
+ def source_info() -> dict[str, str]:
35
+ return {
36
+ "mode": "live",
37
+ "transport": "SRTrain",
38
+ "operator": "주식회사 에스알",
39
+ "endpoint": API_ENDPOINTS["search_schedule"],
40
+ "queue_endpoint": NETFUNNEL_URL,
41
+ "authentication": "anonymous",
42
+ "mutation": "none; timetable search only",
43
+ "booking_url": BOOKING_URL,
44
+ }
104
45
 
105
46
 
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}
47
+ def format_time(value: str) -> str:
48
+ return f"{value[:2]}:{value[2:4]}"
120
49
 
121
50
 
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)
51
+ def normalize_station(value: str) -> str:
52
+ """Accept common "...역" input for a canonical SRT station name."""
53
+ name = value.strip()
54
+ if name in STATION_CODE:
55
+ return name
56
+ if name.endswith("역") and name[:-1] in STATION_CODE:
57
+ return name[:-1]
58
+ return name
125
59
 
126
60
 
127
- def normalize_train(train: SrtTrainLike, index: int) -> dict[str, str | bool | int]:
61
+ def normalize_train(train: Any) -> dict[str, Any]:
128
62
  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(),
63
+ "train_no": str(train.train_number),
64
+ "train_type": str(train.train_name),
65
+ "dep": str(train.dep_station_name),
66
+ "arr": str(train.arr_station_name),
67
+ "dep_date": str(train.dep_date),
68
+ "dep_time": format_time(str(train.dep_time)),
69
+ "arr_time": format_time(str(train.arr_time)),
70
+ "general_seat_available": bool(train.general_seat_available()),
71
+ "special_seat_available": bool(train.special_seat_available()),
142
72
  }
143
73
 
144
74
 
145
- def seat_page_params(train: SrtTrainLike, room: str, car_no: int | None) -> dict[str, str]:
75
+ def search_live_timetable(
76
+ *,
77
+ dep: str,
78
+ arr: str,
79
+ date: str,
80
+ earliest: str,
81
+ latest: str,
82
+ limit: int,
83
+ ) -> dict[str, Any]:
84
+ validate_date(date)
85
+ start = validate_time(earliest)
86
+ end = validate_time(latest)
87
+ if start > end:
88
+ raise ValueError("--time must not be later than --time-limit")
89
+ client = build_client()
90
+ with redirect_stdout(sys.stderr):
91
+ trains = client.search_train(
92
+ dep=normalize_station(dep),
93
+ arr=normalize_station(arr),
94
+ date=date,
95
+ time=start,
96
+ time_limit=end,
97
+ available_only=False,
98
+ )
146
99
  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": "",
100
+ "count": min(len(trains), limit),
101
+ "trains": [normalize_train(train) for train in trains[:limit]],
102
+ "date": date,
103
+ "source": source_info(),
104
+ "schedule_note": "실시간 시간표·좌석 가능 여부 조회이며 예약·좌석 선점은 실행하지 않습니다.",
105
+ "booking_url": BOOKING_URL,
165
106
  }
166
107
 
167
108
 
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
- })
109
+ def validate_date(value: str) -> str:
110
+ if not re.fullmatch(r"\d{8}", value):
111
+ raise ValueError("date must use YYYYMMDD")
112
+ try:
113
+ calendar_date(int(value[:4]), int(value[4:6]), int(value[6:]))
114
+ except ValueError as exc:
115
+ raise ValueError("date must use a valid YYYYMMDD value") from exc
116
+ return value
227
117
 
228
118
 
229
- def print_json(payload: dict[str, object]) -> None:
230
- print(json.dumps(payload, ensure_ascii=False, indent=2))
119
+ def validate_time(value: str) -> str:
120
+ if not re.fullmatch(r"\d{4}", value):
121
+ raise ValueError("time must use HHMM")
122
+ try:
123
+ time.fromisoformat(f"{value[:2]}:{value[2:]}")
124
+ except ValueError as exc:
125
+ raise ValueError("time must use a valid HHMM value") from exc
126
+ return value + "00"
231
127
 
232
128
 
233
129
  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)
130
+ parser = argparse.ArgumentParser(description="SRT live timetable lookup through SRTrain (read-only)")
131
+ commands = parser.add_subparsers(dest="command", required=True)
132
+ search = commands.add_parser("search", help="query the current SRT timetable")
133
+ search.add_argument("--dep", required=True)
134
+ search.add_argument("--arr", required=True)
135
+ search.add_argument("--date", required=True, help="YYYYMMDD")
136
+ search.add_argument("--time", default="0000", help="earliest departure, HHMM")
137
+ search.add_argument("--time-limit", default="2359", help="latest departure, HHMM")
138
+ search.add_argument("--limit", type=int, default=10)
139
+ commands.add_parser("source", help="show the read-only live query endpoint")
255
140
  return parser
256
141
 
257
142
 
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
143
  def main(argv: list[str] | None = None) -> int:
266
- args = build_parser().parse_args(argv)
267
- args.func(args)
268
- return 0
144
+ parser = build_parser()
145
+ args = parser.parse_args(argv)
146
+ try:
147
+ if args.command == "source":
148
+ print(json.dumps(source_info(), ensure_ascii=False, indent=2))
149
+ return 0
150
+ if args.limit < 1 or args.limit > 50:
151
+ raise ValueError("--limit must be between 1 and 50")
152
+ result = search_live_timetable(
153
+ dep=args.dep,
154
+ arr=args.arr,
155
+ date=args.date,
156
+ earliest=args.time,
157
+ latest=args.time_limit,
158
+ limit=args.limit,
159
+ )
160
+ print(json.dumps(result, ensure_ascii=False, indent=2))
161
+ return 0
162
+ except (SRTNetFunnelError, RequestException):
163
+ parser.error(f"SRT timetable lookup unavailable; use the official page: {BOOKING_URL}")
164
+ return 2
165
+ except (SRTError, ValueError) as exc:
166
+ parser.error(str(exc))
167
+ return 2
269
168
 
270
169
 
271
170
  if __name__ == "__main__":
272
- raise SystemExit(main(sys.argv[1:]))
171
+ sys.exit(main())