@nomadamas/k-skill 0.2.2 → 0.2.4

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 (60) hide show
  1. package/package.json +1 -1
  2. package/skills/bunjang-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  3. package/skills/coupang-product-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  4. package/skills/daangn-cars-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  5. package/skills/daangn-jobs-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  6. package/skills/daangn-realty-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  7. package/skills/daangn-used-goods-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  8. package/skills/daishin-report-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  9. package/skills/daiso-product-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  10. package/skills/danawa-price-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  11. package/skills/delivery-tracking/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  12. package/skills/express-bus-booking/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  13. package/skills/flight-ticket-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  14. package/skills/foresttrip-vacancy/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  15. package/skills/intercity-bus-booking/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  16. package/skills/job-posting-match/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  17. package/skills/jobkorea-talent-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  18. package/skills/kakao-bar-nearby/instruction.md +73 -31
  19. package/skills/kakao-bar-nearby/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  20. package/skills/kakao-bar-nearby/skill.json +4 -2
  21. package/skills/kakao-map/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  22. package/skills/kakaotalk-mac/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  23. package/skills/kbl-results/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  24. package/skills/kbo-results/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  25. package/skills/kleague-results/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  26. package/skills/korean-cinema-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  27. package/skills/korean-transit-route/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  28. package/skills/ktx-booking/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  29. package/skills/lck-analytics/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  30. package/skills/lotto-results/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  31. package/skills/market-kurly-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  32. package/skills/myrealtrip-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  33. package/skills/naver-ad-performance/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  34. package/skills/naver-blog-research/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  35. package/skills/naver-news-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  36. package/skills/naver-shopping-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  37. package/skills/ohou-today-deal/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  38. package/skills/olive-young-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  39. package/skills/popbill/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  40. package/skills/saramin-talent-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  41. package/skills/seoul-weather-risk/instruction.md +111 -0
  42. package/skills/seoul-weather-risk/references/admin-dong-place-map.json +1 -0
  43. package/skills/seoul-weather-risk/scripts/seoul_weather_risk.py +503 -0
  44. package/skills/seoul-weather-risk/skill.json +9 -0
  45. package/skills/srt-booking/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  46. package/skills/store-longevity-radar/instruction.md +91 -0
  47. package/skills/store-longevity-radar/scripts/store_longevity_radar.py +230 -0
  48. package/skills/store-longevity-radar/skill.json +8 -0
  49. package/skills/ticket-availability/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  50. package/skills/toss-securities/instruction.md +9 -34
  51. package/skills/toss-securities/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  52. package/skills/toss-securities/skill.json +2 -2
  53. package/skills/used-car-price-search/references/TRADEMARK-LEGAL-STATEMENT.md +9 -0
  54. package/skills/yebigun-training/instruction.md +1 -1
  55. package/skills/catchtable-sniper/instruction.md +0 -270
  56. package/skills/catchtable-sniper/skill.json +0 -9
  57. package/skills/gangnamunni-clinic-search/instruction.md +0 -121
  58. package/skills/gangnamunni-clinic-search/skill.json +0 -9
  59. package/skills/hipass-receipt/instruction.md +0 -97
  60. package/skills/hipass-receipt/skill.json +0 -10
@@ -0,0 +1,230 @@
1
+ # -*- coding: utf-8 -*-
2
+ """store-longevity-radar — 상가(상권)정보 스냅샷 기반 업종 전수 추출 + 장수 점포 매칭.
3
+
4
+ python3 stdlib만 사용한다.
5
+
6
+ subcommands:
7
+ current 최신 공개 스냅샷(무인증 zip)에서 업종코드/상호 키워드로 점포 전수 추출
8
+ match current 결과에 과거 스냅샷 CSV를 매칭해 장수 점포 추출
9
+
10
+ 데이터 출처: 공공데이터포털 「소상공인시장진흥공단_상가(상권)정보」 파일데이터
11
+ (https://www.data.go.kr/data/15083033/fileData.do) — 비회원 다운로드 가능.
12
+ """
13
+ import argparse
14
+ import csv
15
+ import io
16
+ import json
17
+ import math
18
+ import os
19
+ import re
20
+ import sys
21
+ import time
22
+ import urllib.error
23
+ import urllib.request
24
+ import zipfile
25
+
26
+ DATASET_PAGE = "https://www.data.go.kr/data/15083033/fileData.do"
27
+ DOWNLOAD_URL = "https://www.data.go.kr/cmm/cmm/fileDownload.do?atchFileId={fid}&fileDetailSn=1"
28
+ UA = "Mozilla/5.0 (compatible; k-skill-store-longevity-radar)"
29
+ CACHE_DIR = os.path.join(os.path.expanduser("~"), ".cache", "k-skill", "store-longevity-radar")
30
+ CACHE_TTL = 24 * 3600
31
+
32
+ DEFAULT_CODES = ["G21302", "G21306"] # 문구/회화용품 소매업, 장난감 소매업 (2022~ 코드체계)
33
+ DEFAULT_KEYWORDS = ["문구", "문방구", "완구", "장난감"]
34
+ LEGACY_CODES_BY_CURRENT = {
35
+ "G21302": {"D08A01"},
36
+ "G21306": {"D04A01", "D04A02"},
37
+ }
38
+ COLS = ["상가업소번호", "상호명", "상권업종소분류코드", "상권업종소분류명",
39
+ "시도명", "시군구명", "행정동명", "도로명주소", "지번주소", "경도", "위도"]
40
+
41
+
42
+ def log(msg):
43
+ print(msg, file=sys.stderr)
44
+
45
+
46
+ def http_get(url, timeout=60):
47
+ req = urllib.request.Request(url, headers={"User-Agent": UA, "Accept": "*/*"})
48
+ return urllib.request.urlopen(req, timeout=timeout)
49
+
50
+
51
+ def discover_atch_file_id():
52
+ """데이터셋 페이지에서 현재 분기 파일 ID를 찾는다 (분기마다 갱신됨)."""
53
+ html = http_get(DATASET_PAGE).read().decode("utf-8", "replace")
54
+ ids = re.findall(r"FILE_[0-9]{6,}", html)
55
+ if not ids:
56
+ raise SystemExit(json.dumps({"status": "unavailable",
57
+ "note": "데이터셋 페이지에서 파일 ID를 찾지 못함. 수동 확인: " + DATASET_PAGE},
58
+ ensure_ascii=False))
59
+ return ids[0]
60
+
61
+
62
+ def download_latest_zip():
63
+ os.makedirs(CACHE_DIR, exist_ok=True)
64
+ path = os.path.join(CACHE_DIR, "sdc_latest.zip")
65
+ if os.path.exists(path) and time.time() - os.path.getmtime(path) < CACHE_TTL:
66
+ log(f"cache hit: {path}")
67
+ return path
68
+ try:
69
+ fid = discover_atch_file_id()
70
+ log(f"downloading {fid} (수백 MB, 수 분 소요) ...")
71
+ tmp = path + ".part"
72
+ with http_get(DOWNLOAD_URL.format(fid=fid), timeout=1800) as r, open(tmp, "wb") as f:
73
+ while True:
74
+ chunk = r.read(1 << 20)
75
+ if not chunk:
76
+ break
77
+ f.write(chunk)
78
+ os.replace(tmp, path)
79
+ except (TimeoutError, urllib.error.URLError) as exc:
80
+ raise SystemExit(json.dumps({
81
+ "status": "unavailable",
82
+ "note": f"공공데이터포털 접속 또는 다운로드 실패: {exc}. 수동 확인: {DATASET_PAGE}",
83
+ }, ensure_ascii=False)) from None
84
+ return path
85
+
86
+
87
+ def norm_name(s):
88
+ s = re.sub(r"[\s()\[\]{}\-_.·&']", "", s or "")
89
+ return re.sub(r"(점|본점|지점)$", "", s)
90
+
91
+
92
+ def row_matches(d, codes, keywords):
93
+ if d.get("상권업종소분류코드") in codes:
94
+ return True
95
+ name = d.get("상호명", "")
96
+ return any(k in name for k in keywords)
97
+
98
+
99
+ def historical_codes(codes):
100
+ expanded = set(codes)
101
+ for code in codes:
102
+ expanded.update(LEGACY_CODES_BY_CURRENT.get(code, ()))
103
+ return expanded
104
+
105
+
106
+ def iter_csv(fobj, delimiter):
107
+ reader = csv.reader(io.TextIOWrapper(fobj, encoding="utf-8", errors="replace"),
108
+ delimiter=delimiter)
109
+ header = next(reader)
110
+ idx = {h: i for i, h in enumerate(header)}
111
+ for row in reader:
112
+ yield {h: (row[i] if i < len(row) else "") for h, i in idx.items() if h in COLS}
113
+
114
+
115
+ def extract_current(args):
116
+ path = args.zip or download_latest_zip()
117
+ rows = []
118
+ with zipfile.ZipFile(path) as z:
119
+ for info in z.infolist():
120
+ if not info.filename.endswith(".csv"):
121
+ continue
122
+ if args.sido and not any(s in info.filename for s in args.sido):
123
+ continue
124
+ with z.open(info) as f:
125
+ for d in iter_csv(f, ","):
126
+ if row_matches(d, set(args.code), args.keyword):
127
+ rows.append(d)
128
+ return rows
129
+
130
+
131
+ def load_old(paths, codes, keywords):
132
+ rows = []
133
+ for p in paths:
134
+ with open(p, "rb") as f:
135
+ head = f.read(4096).decode("utf-8", "replace")
136
+ delim = "|" if head.count("|") > head.count(",") else ","
137
+ with open(p, "rb") as f:
138
+ for d in iter_csv(f, delim):
139
+ if row_matches(d, codes, keywords):
140
+ rows.append(d)
141
+ return rows
142
+
143
+
144
+ def dist_m(a, b):
145
+ try:
146
+ return math.hypot((float(a["경도"]) - float(b["경도"])) * 88800,
147
+ (float(a["위도"]) - float(b["위도"])) * 111000)
148
+ except (ValueError, KeyError):
149
+ return float("inf")
150
+
151
+
152
+ def match_longevity(current, old, max_dist):
153
+ old_by_id = {o["상가업소번호"]: o for o in old if o.get("상가업소번호")}
154
+ old_by_name = {}
155
+ for o in old:
156
+ old_by_name.setdefault(norm_name(o.get("상호명")), []).append(o)
157
+ out = []
158
+ for c in current:
159
+ o, how = old_by_id.get(c.get("상가업소번호")), "상가업소번호"
160
+ if not o:
161
+ best = None
162
+ for cand in old_by_name.get(norm_name(c.get("상호명")), []):
163
+ d = dist_m(cand, c)
164
+ if d <= max_dist and (best is None or d < best[1]):
165
+ best = (cand, d)
166
+ if best:
167
+ o, how = best[0], f"상호+좌표({best[1]:.0f}m)"
168
+ if o:
169
+ rec = dict(c)
170
+ rec["과거상호"] = o.get("상호명", "")
171
+ rec["과거업종"] = o.get("상권업종소분류명", "")
172
+ rec["매칭방법"] = how
173
+ out.append(rec)
174
+ return out
175
+
176
+
177
+ def write_out(rows, out, fmt):
178
+ if fmt == "json" or not out:
179
+ payload = json.dumps({"count": len(rows), "rows": rows}, ensure_ascii=False)
180
+ if out:
181
+ with open(out, "w", encoding="utf-8") as f:
182
+ f.write(payload)
183
+ else:
184
+ print(payload)
185
+ return
186
+ cols = list(rows[0].keys()) if rows else COLS
187
+ with open(out, "w", encoding="utf-8-sig", newline="") as f:
188
+ w = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore")
189
+ w.writeheader()
190
+ w.writerows(rows)
191
+ print(json.dumps({"count": len(rows), "saved": out}, ensure_ascii=False))
192
+
193
+
194
+ def main():
195
+ p = argparse.ArgumentParser(description=__doc__)
196
+ sub = p.add_subparsers(dest="cmd", required=True)
197
+ common = argparse.ArgumentParser(add_help=False)
198
+ common.add_argument("--code", action="append", default=None,
199
+ help="상권업종소분류코드 (반복 지정, 기본 G21302/G21306)")
200
+ common.add_argument("--keyword", action="append", default=None,
201
+ help="상호 키워드 (반복 지정, 기본 문구/문방구/완구/장난감)")
202
+ common.add_argument("--sido", action="append", default=None,
203
+ help="시도명 부분 문자열 필터 (예: 서울, 부산). 생략 시 전국")
204
+ common.add_argument("--zip", help="이미 받아둔 최신 스냅샷 zip 경로 (생략 시 자동 다운로드+캐시)")
205
+ common.add_argument("--out", help="출력 파일 경로 (생략 시 stdout JSON)")
206
+ common.add_argument("--format", choices=["csv", "json"], default="csv")
207
+
208
+ sub.add_parser("current", parents=[common], help="최신 스냅샷 업종 전수 추출")
209
+ mp = sub.add_parser("match", parents=[common], help="과거 스냅샷과 매칭해 장수 점포 추출")
210
+ mp.add_argument("--old-csv", action="append", required=True,
211
+ help="과거 상가업소 CSV 경로 (반복 지정 가능, '|'/',' 자동 감지)")
212
+ mp.add_argument("--max-dist", type=float, default=150.0,
213
+ help="상호 동일 시 허용 좌표 거리(m), 기본 150")
214
+
215
+ args = p.parse_args()
216
+ args.code = args.code or DEFAULT_CODES
217
+ args.keyword = args.keyword or DEFAULT_KEYWORDS
218
+
219
+ current = extract_current(args)
220
+ log(f"current: {len(current)}건")
221
+ if args.cmd == "current":
222
+ write_out(current, args.out, args.format)
223
+ return
224
+ old = load_old(args.old_csv, historical_codes(set(args.code)), args.keyword)
225
+ log(f"old: {len(old)}건")
226
+ write_out(match_longevity(current, old, args.max_dist), args.out, args.format)
227
+
228
+
229
+ if __name__ == "__main__":
230
+ main()
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "store-longevity-radar",
3
+ "description": "소상공인시장진흥공단 상가(상권)정보 공개파일(무인증)로 업종·상호 키워드에 맞는 전국 점포 전수를 뽑고, 과거 스냅샷과 상호+좌표 매칭해 'N년 전에도 존재했고 지금도 영업 중'인 장수 점포 리스트를 추출한다. 사업자등록번호 없이 상호 기준이며 개업일이 아닌 최초 관측 시점 하한을 제공한다.",
4
+ "profiles": [
5
+ "lookup"
6
+ ],
7
+ "frontmatter": "name: store-longevity-radar\ndescription: 소상공인시장진흥공단 상가(상권)정보 공개파일(무인증)로 업종·상호 키워드에 맞는 전국 점포 전수를 뽑고, 과거 스냅샷과 상호+좌표 매칭해 'N년 전에도 존재했고 지금도 영업 중'인 장수 점포 리스트를 추출한다. 사업자등록번호 없이 상호 기준이며 개업일이 아닌 최초 관측 시점 하한을 제공한다.\nlicense: MIT\nmetadata:\n category: business\n locale: ko-KR\n phase: v1"
8
+ }
@@ -0,0 +1,9 @@
1
+ # 상표 사용 법적 고지 — `ticket-availability`
2
+
3
+ 이 스킬에서 `YES24`와 `인터파크` 명칭은 공연 일정·등급별 잔여석 조회의 **대상 티켓 서비스**를 식별하기 위해 사용한다. k-skill의 출처를 각 서비스 운영사로 표시하려는 사용이 아니다.
4
+
5
+ 대법원 2005. 6. 10. 선고 [2005도1637 판결](https://www.law.go.kr/LSW/precInfoP.do?precSeq=83920)은 타인의 표장을 출처표시가 아니라 상품 기능 또는 적용 기종을 밝히기 위해 사용하고 상표 사용으로 인식될 수 없는 경우 침해가 아니라고 판시했다. [상표법 제2조](https://www.law.go.kr/법령/상표법/제2조), [제89조](https://www.law.go.kr/법령/상표법/제89조), [제90조](https://www.law.go.kr/법령/상표법/제90조), [제108조](https://www.law.go.kr/법령/상표법/제108조)와 대법원 [2011다18802](https://www.law.go.kr/LSW/precInfoP.do?precSeq=167457), [2019후10418](https://law.go.kr/LSW/precInfoP.do?mode=0&precSeq=230725) 판결에 따라 실제 거래계에서 출처표시로 기능하는지는 표시 태양과 사용 경위 등을 종합해 판단해야 한다.
6
+
7
+ 따라서 이 명칭은 필요한 범위의 평문으로만 사용하고, 별도 근거 없이 로고, 공식·제휴·후원·인증·파트너 표현을 사용하지 않는다. 각 상표의 권리는 해당 권리자에게 있다. 이 문서는 공연·좌석 데이터의 저작권·데이터베이스권, 약관·계약, 부정경쟁방지법 또는 접근 방식의 적법성을 판단하지 않는다.
8
+
9
+ 전체 검토: [제3자 상표의 기능 설명·호환 대상 표시 검토](https://github.com/NomaDamas/k-skill/blob/dev/docs/legal/trademark-use-review.md)
@@ -2,10 +2,7 @@
2
2
 
3
3
  ## What this skill does
4
4
 
5
- 토스증권 **조회 전용(read-only)** 흐름을 실행한다. 경로가 있다.
6
-
7
- 1. **공식 Open API (권장)** — 토스증권 공식 Open API(`https://openapi.tossinvest.com`)를 OAuth 2.0 Client Credentials 토큰으로 호출.
8
- 2. **tossctl fallback** — 공식 credentials가 없을 때 `JungHoonGhae/tossinvest-cli` 의 `tossctl` 을 사용.
5
+ 토스증권 **공식 Open API 전용 조회(read-only)** 흐름을 실행한다. 토스증권 공식 Open API(`https://openapi.tossinvest.com`)를 OAuth 2.0 Client Credentials 토큰으로 직접 호출한다.
9
6
 
10
7
  조회 항목:
11
8
 
@@ -13,7 +10,6 @@
13
10
  - 시세(현재가/호가/체결/상하한가/캔들) / 종목 정보 / 매수 유의사항
14
11
  - 환율 / 장 운영 캘린더(KR·US)
15
12
  - 대기중 주문 조회 / 주문 상세 / 매수가능금액 / 판매가능수량 / 수수료
16
- - (tossctl fallback) 계좌 요약, 포트폴리오 비중, 관심종목
17
13
 
18
14
  ## When to use
19
15
 
@@ -22,7 +18,7 @@
22
18
  - "대기중 주문 조회해줘"
23
19
  - "원달러 환율 알려줘"
24
20
 
25
- ## 1. Prefer the official Open API
21
+ ## Use only the official Open API
26
22
 
27
23
  ### Prerequisites
28
24
 
@@ -36,11 +32,10 @@
36
32
  | `TOSSINVEST_CLIENT_ID` | client id (필수) |
37
33
  | `TOSSINVEST_CLIENT_SECRET` | client secret (필수) |
38
34
  | `TOSSINVEST_ACCOUNT` | accountSeq. 계좌·자산·주문조회에 필요 (선택) |
39
- | `TOSSINVEST_API_BASE_URL` | 기본 `https://openapi.tossinvest.com` (선택) |
40
35
 
41
36
  ### Workflow
42
37
 
43
- helper는 내부적으로 `POST /oauth2/token` 으로 토큰을 발급(Client Credentials)받아 `Authorization: Bearer` 로 호출한다. 계좌·자산·주문조회 API는 `X-Tossinvest-Account` 헤더가 추가로 필요하다.
38
+ helper는 내부적으로 `POST /oauth2/token` 으로 토큰을 발급(Client Credentials)받아 `Authorization: Bearer` 로 호출한다. API origin은 `https://openapi.tossinvest.com` 으로 고정되며 다른 host로 변경할 수 없다. 계좌·자산·주문조회 API는 `X-Tossinvest-Account` 헤더가 추가로 필요하다.
44
39
 
45
40
  ```js
46
41
  const {
@@ -70,31 +65,11 @@ main().catch((error) => {
70
65
  - `401` 은 토큰을 1회 재발급해 재시도한다.
71
66
  - `client_secret`/토큰은 에러 메시지에서 마스킹된다.
72
67
 
73
- ## 2. tossctl fallback
74
-
75
- 공식 credentials가 없으면 비공식 `tossctl` 을 fallback으로 쓴다.
76
-
77
- ### Install `tossctl` first when missing
78
-
79
- ```bash
80
- brew tap JungHoonGhae/tossinvest-cli
81
- brew install tossctl
82
- tossctl doctor
83
- tossctl auth doctor
84
- tossctl auth login
85
- ```
86
-
87
- 로그인 세션이 없으면 먼저 위 흐름을 끝낸다. 다른 비공식 크롤링이나 임의 HTTP 재구현으로 우회하지 않는다.
88
-
89
- 지원하는 read-only 명령:
90
-
91
- - `tossctl account summary --output json`
92
- - `tossctl portfolio positions --output json`
93
- - `tossctl quote get TSLA --output json`
94
- - `tossctl watchlist list --output json`
95
- - `tossctl orders completed --market all --output json`
68
+ ## Official-only boundary
96
69
 
97
- 패키지 wrapper(`getAccountSummary`, `getPortfolioPositions`, `getQuote`, `listWatchlist` 등)도 그대로 있다.
70
+ - 공식 API credentials가 없으면 `TossCredentialsError` 종료하고 필요한 환경변수를 안내한다.
71
+ - 공식 API가 제공하지 않는 기능은 지원하지 않는다고 명확히 답한다.
72
+ - 비공식 CLI, 로그인 세션 재사용, 크롤링, 임의 HTTP 호출로 우회하지 않는다.
98
73
 
99
74
  ## Answer conservatively
100
75
 
@@ -104,7 +79,7 @@ tossctl auth login
104
79
 
105
80
  ## Done when
106
81
 
107
- - 공식 API credentials(또는 tossctl 로그인) 상태가 확인되었다.
82
+ - 공식 API credentials 상태가 확인되었다.
108
83
  - 요청에 맞는 read-only 호출을 실행했다.
109
84
  - 결과를 한국어로 짧게 정리했다.
110
85
 
@@ -112,5 +87,5 @@ tossctl auth login
112
87
 
113
88
  - 공식 API credentials(`TOSSINVEST_CLIENT_ID`/`SECRET`)가 없으면 `TossCredentialsError` 로 명확히 실패한다.
114
89
  - 계좌·자산·주문조회 helper에 `X-Tossinvest-Account` 가 없으면 네트워크 호출 전에 실패한다.
115
- - tossctl fallback은 `auth login` 전이면 계좌/포트폴리오 조회가 실패할 있다.
90
+ - 공식 API가 지원하지 않는 요청은 비공식 경로로 우회하지 않고 지원 불가로 종료한다.
116
91
  - 계좌/주문 정보는 민감하므로 출력 범위를 과도하게 넓히지 않는다.
@@ -0,0 +1,9 @@
1
+ # 상표 사용 법적 고지 — `toss-securities`
2
+
3
+ 이 스킬에서 `토스증권` 및 `Toss Securities` 명칭은 계좌·보유주식·시세·주문 조회의 **대상 증권 서비스**를 식별하기 위해 사용한다. k-skill의 출처를 토스증권으로 표시하려는 사용이 아니다.
4
+
5
+ 대법원 2005. 6. 10. 선고 [2005도1637 판결](https://www.law.go.kr/LSW/precInfoP.do?precSeq=83920)은 타인의 표장을 출처표시가 아니라 상품 기능 또는 적용 기종을 밝히기 위해 사용하고 상표 사용으로 인식될 수 없는 경우 침해가 아니라고 판시했다. [상표법 제2조](https://www.law.go.kr/법령/상표법/제2조), [제89조](https://www.law.go.kr/법령/상표법/제89조), [제90조](https://www.law.go.kr/법령/상표법/제90조), [제108조](https://www.law.go.kr/법령/상표법/제108조)와 대법원 [2011다18802](https://www.law.go.kr/LSW/precInfoP.do?precSeq=167457), [2019후10418](https://law.go.kr/LSW/precInfoP.do?mode=0&precSeq=230725) 판결에 따라 실제 거래계에서 출처표시로 기능하는지는 표시 태양과 사용 경위 등을 종합해 판단해야 한다.
6
+
7
+ 따라서 이 명칭은 필요한 범위의 평문으로만 사용하고, 별도 근거 없이 로고, 공식·제휴·후원·인증·파트너 표현을 사용하지 않는다. 각 상표의 권리는 해당 권리자에게 있다. 이 문서는 금융규제, 투자 적합성, API·서비스 약관, 개인정보, 계좌 접근 또는 개별 거래의 적법성을 판단하지 않는다.
8
+
9
+ 전체 검토: [제3자 상표의 기능 설명·호환 대상 표시 검토](https://github.com/NomaDamas/k-skill/blob/dev/docs/legal/trademark-use-review.md)
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "toss-securities",
3
- "description": "토스증권 조회형 질문을 공식 Open API(OAuth2)로 우선 처리하고, 공식 credentials가 없으면 tossinvest-cli의 tossctl을 fallback으로 써서 계좌, 보유주식, 시세/종목/시장정보, 주문조회를 안전한 read-only 흐름으로 조회한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
3
+ "description": "토스증권 공식 Open API(OAuth2)로 계좌, 보유주식, 시세/종목/시장정보, 주문조회를 안전한 read-only 흐름으로 조회한다. 공식 credentials가 없거나 공식 API가 지원하지 않는 기능은 비공식 경로로 우회하지 않는다.",
4
4
  "profiles": [
5
5
  "vault",
6
6
  "action:account"
7
7
  ],
8
- "frontmatter": "name: toss-securities\ndescription: 토스증권 조회형 질문을 공식 Open API(OAuth2)로 우선 처리하고, 공식 credentials가 없으면 tossinvest-cli의 tossctl을 fallback으로 써서 계좌, 보유주식, 시세/종목/시장정보, 주문조회를 안전한 read-only 흐름으로 조회한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: finance\n locale: ko-KR\n phase: v1"
8
+ "frontmatter": "name: toss-securities\ndescription: 토스증권 공식 Open API(OAuth2)로 계좌, 보유주식, 시세/종목/시장정보, 주문조회를 안전한 read-only 흐름으로 조회한다. 공식 credentials가 없거나 공식 API가 지원하지 않는 기능은 비공식 경로로 우회하지 않는다.\nlicense: MIT\nmetadata:\n category: finance\n locale: ko-KR\n phase: v1"
9
9
  }
@@ -0,0 +1,9 @@
1
+ # 상표 사용 법적 고지 — `used-car-price-search`
2
+
3
+ 이 스킬에서 `SK렌터카` 명칭은 중고차 재고·가격·인수가 조회의 **대상 판매·렌터카 서비스**를 식별하기 위해 사용한다. k-skill의 출처를 SK렌터카 운영사로 표시하려는 사용이 아니다.
4
+
5
+ 대법원 2005. 6. 10. 선고 [2005도1637 판결](https://www.law.go.kr/LSW/precInfoP.do?precSeq=83920)은 타인의 표장을 출처표시가 아니라 상품 기능 또는 적용 기종을 밝히기 위해 사용하고 상표 사용으로 인식될 수 없는 경우 침해가 아니라고 판시했다. [상표법 제2조](https://www.law.go.kr/법령/상표법/제2조), [제89조](https://www.law.go.kr/법령/상표법/제89조), [제90조](https://www.law.go.kr/법령/상표법/제90조), [제108조](https://www.law.go.kr/법령/상표법/제108조)와 대법원 [2011다18802](https://www.law.go.kr/LSW/precInfoP.do?precSeq=167457), [2019후10418](https://law.go.kr/LSW/precInfoP.do?mode=0&precSeq=230725) 판결에 따라 실제 거래계에서 출처표시로 기능하는지는 표시 태양과 사용 경위 등을 종합해 판단해야 한다.
6
+
7
+ 따라서 이 명칭은 필요한 범위의 평문으로만 사용하고, 별도 근거 없이 로고, 공식·제휴·후원·인증·파트너 표현을 사용하지 않는다. 각 상표의 권리는 해당 권리자에게 있다. 이 문서는 차량정보의 저작권·데이터베이스권, 매매·렌탈 계약, 약관·계약, 소비자보호 또는 접근 방식의 적법성을 판단하지 않는다.
8
+
9
+ 전체 검토: [제3자 상표의 기능 설명·호환 대상 표시 검토](https://github.com/NomaDamas/k-skill/blob/dev/docs/legal/trademark-use-review.md)
@@ -11,7 +11,7 @@
11
11
  - 필요하면 로컬에 연도별 기록을 추가로 저장(`record`)하고 비교(`diff`)할 수도 있다 (사이트가 더 오래된 기록을 보여주지 않게 되거나, 메모를 남기고 싶을 때를 위한 보조 기능).
12
12
  - 범용 페이지 조회(`inspect`)로 아직 분류 안 된 다른 페이지 구조를 확인할 수도 있다.
13
13
 
14
- `hipass-receipt`와 동일한 설계 원칙을 따른다: **로그인은 항상 사용자가 직접 하고, 이 스킬은 로그인된 세션에서 조회만 한다.**
14
+ 설계 원칙은 명확하다: **로그인은 항상 사용자가 직접 하고, 이 스킬은 로그인된 세션에서 조회만 한다.**
15
15
 
16
16
  ## Hard limits
17
17
 
@@ -1,270 +0,0 @@
1
- # catchtable-sniper
2
-
3
- ## 📋 기본 정보
4
-
5
- - **스킬명**: catchtable-sniper
6
- - **라이선스**: MIT
7
- - **단계**: v1
8
- - **카테고리**: lifestyle / food
9
- - **로케일**: ko-KR
10
- - **요구사항**: Chrome MCP, 캐치테이블 로그인된 Chrome 세션
11
-
12
- ---
13
-
14
- ## 🎯 주요 기능
15
-
16
- 캐치테이블에서 원하는 식당의 빈자리(취소 슬롯)를 30초 간격으로 감시하다가 발견하는 즉시 자동 예약합니다.
17
- 멀티 타겟 동시 감시, 예약 오픈런 모드, 인원 유연 매칭, Dry-run 알림 전용 모드를 지원합니다.
18
-
19
- ---
20
-
21
- ## ✅ 적합한 사용 사례
22
-
23
- - `"온지음 5월 토요일 저녁 2인 빈자리 나오면 예약해줘"`
24
- - `"온지음, 밍글스, 라연 중 5월 주말 2인 아무데나 먼저 뜨는 거 잡아줘"` ← 멀티 타겟
25
- - `"라연 5월 예약 오픈이 4월 30일 오전 10시야, 그때 맞춰서 잡아줘"` ← 오픈런 모드
26
- - `"스시야마 이번달 안에 2인 — 못 잡으면 4인 있으면 알려줘"` ← 인원 유연
27
- - `"밍글스 빈자리 뜨면 예약은 내가 할게 알림만 줘"` ← Dry-run 모드
28
- - `"https://app.catchtable.co.kr/ct/shop/mingles 토요일 4명 자동예약"`
29
-
30
- ---
31
-
32
- ## ❌ 부적합한 사용 사례
33
-
34
- - 로그인 자동화 (카카오/네이버 로그인은 직접 해야 함)
35
- - 선결제 식당의 결제 정보 자동 입력 (결제 단계는 사람이 직접)
36
- - 캐치테이블 외 플랫폼 예약 (네이버 예약, 식신 등)
37
- - 30초 미만 폴링 간격 (서버 부하 방지)
38
-
39
- ---
40
-
41
- ## 🔧 기술 요구사항
42
-
43
- - **Chrome MCP** 연결 필수
44
- - 캐치테이블(`app.catchtable.co.kr`)에 **로그인된 Chrome 세션** 필요
45
- - 별도 API 키, 패키지 설치 불필요
46
-
47
- ---
48
-
49
- ## 🔐 인증 처리
50
-
51
- 이 스킬은 이미 Chrome에 로그인된 세션을 그대로 사용합니다.
52
- 로그인 정보를 스킬에 전달하지 않습니다.
53
-
54
- 로그인 안 된 경우:
55
- ```
56
- "캐치테이블에 로그인되어 있지 않습니다.
57
- Chrome에서 캐치테이블에 카카오/네이버 로그인 후 다시 실행해주세요."
58
- ```
59
- → 스킬 중단. 로그인 자동화 없음.
60
-
61
- ---
62
-
63
- ## 🗂️ 입력 파싱
64
-
65
- 사용자 입력에서 다음을 추출한다:
66
-
67
- | 항목 | 예시 | 필수 여부 |
68
- |------|------|----------|
69
- | 식당명 또는 URL | `"온지음"` / `app.catchtable.co.kr/ct/shop/onjium` | 필수 (복수 가능) |
70
- | 날짜 | `"5월 3일"`, `"이번 주 토요일"`, `"5월 주말 전체"` | 필수 |
71
- | 인원 | `"2명"`, `"4인"` | 필수 |
72
- | 시간대 | `"저녁"`, `"19시 이후"` | 선택 (없으면 전체) |
73
- | 모드 | `"알림만"`, `"dry-run"` | 선택 (없으면 자동예약) |
74
- | 인원 유연 | `"2인 없으면 4인도 괜찮아"` | 선택 |
75
- | 오픈 시간 | `"4월 30일 오전 10시 오픈"` | 선택 (오픈런 모드) |
76
- | 폴링 간격 | `"30초마다"` | 선택 (기본: 30초) |
77
-
78
- **멀티 타겟 감지**: 식당명이 쉼표/슬래시로 구분되거나 "중 아무데나", "먼저 뜨는 거" 표현이 있으면 멀티 타겟 모드로 전환.
79
-
80
- ---
81
-
82
- ## 📊 실행 플로우
83
-
84
- ### STEP 1 — 브라우저 준비 및 로그인 확인
85
-
86
- Chrome MCP로 캐치테이블 접속:
87
- ```
88
- navigate: https://app.catchtable.co.kr
89
- ```
90
- MY 탭에서 로그인 상태 확인. 미로그인 시 중단.
91
-
92
- ---
93
-
94
- ### STEP 2 — 모드 분기
95
-
96
- ```
97
- 입력 파싱 완료
98
- ├─ 오픈 시간 명시됨 → STEP 2-A (오픈런 모드)
99
- └─ 오픈 시간 없음 → STEP 2-B (취소 스나이핑 모드)
100
- ```
101
-
102
- #### STEP 2-A: 오픈런 모드
103
-
104
- 예약 오픈 시간까지 대기:
105
- ```
106
- [10:00:00 오픈 예정] 현재 09:58:42 — 77초 후 오픈
107
- [10:00:00] ✅ 오픈 시각 도달 — 즉시 예약 시도
108
- ```
109
- 오픈 시각 정각에 날짜 선택 → 슬롯 클릭 → 예약 폼 진입.
110
- 슬롯이 이미 마감이면 → 취소 스나이핑 모드(STEP 2-B)로 자동 전환.
111
-
112
- #### STEP 2-B: 취소 스나이핑 모드 (폴링 루프)
113
-
114
- ```
115
- while 빈자리 없음:
116
- {폴링 간격}초 대기
117
- 페이지 새로고침 또는 날짜 재클릭
118
- 슬롯 파싱
119
- 빈자리 발견 → STEP 3
120
- ```
121
-
122
- ---
123
-
124
- ### STEP 3 — 멀티 타겟 처리
125
-
126
- **단일 타겟**: 해당 식당 슬롯 확인.
127
-
128
- **멀티 타겟**: 지정된 식당들을 순차 순회하며 슬롯 확인.
129
- ```
130
- [14:23:15] 온지음 5/3 확인 중... 없음
131
- [14:23:17] 밍글스 5/3 확인 중... 없음
132
- [14:23:19] 라연 5/3 확인 중... 없음 (30초 후 재시도)
133
- [14:23:49] ✅ 밍글스 5/3 19:30 빈자리 발견! — 예약 시작
134
- ```
135
- 한 곳에서 슬롯 발견 시 나머지 감시 즉시 중단 → 발견된 식당 예약 진행.
136
-
137
- ---
138
-
139
- ### STEP 4 — 인원 유연 매칭
140
-
141
- 지정 인원(예: 2인) 슬롯이 없을 경우:
142
-
143
- ```
144
- if 인원_유연 == True:
145
- 대안_인원(예: 4인) 슬롯 확인
146
- 발견 시:
147
- "2인 슬롯은 없지만 4인 슬롯(19:00)이 있습니다.
148
- 4인으로 예약할까요? (예/아니오)"
149
- → 사용자 확인 후 진행
150
- ```
151
-
152
- ---
153
-
154
- ### STEP 5 — 예약 진행 (모드 분기)
155
-
156
- **Dry-run 모드** (`"알림만"` / `"dry-run"` 입력 시):
157
- ```
158
- ✅ 빈자리 발견! 예약은 진행하지 않습니다.
159
- 식당: 밍글스
160
- 날짜: 5월 3일(토)
161
- 시간: 19:30
162
- 인원: 2명
163
- → 지금 바로 예약하시겠습니까? (예/아니오)
164
- ```
165
- → 예약 여부는 사람이 결정.
166
-
167
- **자동예약 모드** (기본):
168
-
169
- 빈 슬롯 버튼 즉시 클릭 → 예약 폼 진입.
170
-
171
- 폼 자동 입력:
172
- - 인원수: 지정한 인원 선택
173
- - 방문 목적: "식사" (기본값)
174
- - 주의사항 동의: 전체 동의 체크
175
- - 예약자 정보: 앱 저장 정보 자동 사용
176
-
177
- **선결제 식당인 경우**:
178
- ```
179
- "빈자리를 발견했습니다! 결제가 필요합니다.
180
- 결제 금액: {금액}원
181
- 지금 결제를 진행할까요? (예/아니오)"
182
- ```
183
- → 결제 정보 자동 입력 없음. 사용자 확인 후 결제 진행.
184
-
185
- **무료 예약**: "예약하기" 최종 확인 버튼 클릭.
186
-
187
- ---
188
-
189
- ### STEP 6 — 완료 확인
190
-
191
- ```
192
- 🎉 예약 완료!
193
-
194
- 식당: {식당명}
195
- 날짜: {날짜}
196
- 시간: {시간}
197
- 인원: {인원}명
198
- 모드: {자동예약 / Dry-run}
199
- 예약번호: {예약번호}
200
-
201
- 캐치테이블 앱 > MY > 예약내역에서 확인 가능합니다.
202
- ```
203
-
204
- ---
205
-
206
- ## 💡 중간 상태 출력 형식
207
-
208
- ```
209
- [14:23:15] 밍글스 5/3 저녁 슬롯 확인 중... 빈자리 없음 (30초 후 재시도)
210
- [14:23:45] 온지음 5/3 저녁 슬롯 확인 중... 빈자리 없음
211
- [14:24:15] ✅ 밍글스 5/3 19:30 (2인) 빈자리 발견! — 예약 시작
212
- ```
213
-
214
- ---
215
-
216
- ## ⚙️ 설정값
217
-
218
- | 항목 | 기본값 | 범위 |
219
- |------|--------|------|
220
- | 폴링 간격 | 30초 | 30초 이상 |
221
- | 최대 감시 시간 | 2시간 | — |
222
- | 멀티 타겟 최대 수 | 5개 | — |
223
-
224
- 2시간 초과 시:
225
- ```
226
- "2시간 동안 빈자리가 없었습니다. 계속 시도할까요? (예/아니오)"
227
- ```
228
-
229
- ---
230
-
231
- ## 🚨 에러 핸들링
232
-
233
- | 상황 | 대응 |
234
- |------|------|
235
- | 식당 페이지 404 | "식당을 찾을 수 없습니다. 이름을 다시 확인해주세요." |
236
- | 예약 오픈 전 | 오픈 일정 안내 후 오픈런 모드로 전환 제안 |
237
- | 슬롯 클릭 후 이미 마감 | 즉시 재폴링 재개 |
238
- | 네트워크 오류 | 10초 후 재시도, 3회 연속 실패 시 사용자 알림 |
239
- | 멀티 타겟 중 일부 404 | 해당 식당 제외, 나머지 계속 감시 |
240
- | 2시간 초과 | "계속 시도할까요?" 확인 후 연장 또는 종료 |
241
-
242
- ---
243
-
244
- ## ✨ 완료 기준
245
-
246
- 다음 중 하나:
247
- - 예약 완료 화면 확인 + 예약번호 수집
248
- - Dry-run 모드에서 빈자리 발견 및 사용자 알림 완료
249
- - 사용자가 명시적으로 중단 요청
250
-
251
- ---
252
-
253
- ## 사용 예시
254
-
255
- ```
256
- "온지음 5월 10일 저녁 2인 빈자리 나오면 예약해줘"
257
- "온지음, 밍글스, 라연 5월 토요일 저녁 2인 중 아무데나 먼저 뜨는 거 잡아줘"
258
- "라연 5월 예약이 4월 30일 오전 10시 오픈이야, 그때 맞춰 2인 잡아줘"
259
- "스시야마 이번달 2인 — 없으면 4인도 괜찮아, dry-run으로"
260
- "https://app.catchtable.co.kr/ct/shop/mingles 토요일 4명 자동예약"
261
- ```
262
-
263
- ---
264
-
265
- ## ⚠️ 주의사항
266
-
267
- - Chrome에 캐치테이블 로그인 세션이 있어야 동작합니다.
268
- - 선결제 식당의 결제 정보는 직접 입력해야 합니다.
269
- - 폴링 간격은 최소 30초를 유지합니다 (서버 부하 방지).
270
- - 캐치테이블 이용약관을 준수하는 범위에서 사용하세요.
@@ -1,9 +0,0 @@
1
- {
2
- "name": "catchtable-sniper",
3
- "description": "Monitor Catchtable for open reservation slots and attempt booking using a logged-in Chrome session. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
4
- "profiles": [
5
- "browser",
6
- "action:booking"
7
- ],
8
- "frontmatter": "name: catchtable-sniper\ndescription: Monitor Catchtable for open reservation slots and attempt booking using a logged-in Chrome session. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: lifestyle\n subcategory: food\n locale: ko-KR\n phase: v1\n requires:\n - Chrome MCP\n - Logged-in Catchtable Chrome session"
9
- }