@nomadamas/k-skill 0.4.1 → 0.4.3
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.
- package/package.json +1 -1
- package/skills/biz-health-check/instruction.md +7 -0
- package/skills/coupang-product-search/instruction.md +59 -188
- package/skills/coupang-product-search/skill.json +6 -2
- package/skills/court-payment-order-assistant/instruction.md +9 -0
- package/skills/daiso-product-search/instruction.md +11 -1
- package/skills/fsc-corporate-info/instruction.md +2 -1
- package/skills/fsc-corporate-info/scripts/test_fsc_corporate_info.py +42 -0
- package/skills/g2b-sanctioned-supplier/instruction.md +2 -1
- package/skills/g2b-sanctioned-supplier/scripts/test_g2b_sanctioned_supplier.py +33 -0
- package/skills/market-kurly-search/instruction.md +9 -1
- package/skills/national-pension-workplace/instruction.md +6 -0
- package/skills/nts-tax-delinquency/instruction.md +2 -1
- package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +15 -1
- package/skills/nts-tax-delinquency/scripts/test_nts_tax_delinquency.py +32 -0
- package/skills/store-longevity-radar/scripts/__pycache__/store_longevity_download.cpython-312.pyc +0 -0
- package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +0 -146
package/package.json
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
공시 유무는 기존 `k-dart` 스킬을 함께 쓰면 된다.
|
|
17
17
|
|
|
18
|
+
## 법적 안전 운영 기준
|
|
19
|
+
|
|
20
|
+
1. **본인의 실사·거래 확인 목적으로만 쓴다.** 거래 상대방의 사업 상태를 확인하는 실사(듀 디리전스) 참고 도구로 한정하고, 특정 개인사업자에 대한 신용 평가·등급화·명단화 서비스로 운영하지 않는다.
|
|
21
|
+
2. **이미 공개된 공공 정보만 조합한다.** 각 섹션은 국세청·국민연금공단·금융위원회·조달청·지방행정이 공식 공개한 정보를 조회시점 그대로 병렬할 뿐, 비공개 정보를 수집하거나 새로운 개인정보를 생성하지 않는다.
|
|
22
|
+
3. **대량·정기 수집에는 목적과 근거를 먼저 확인한다.** 특정 사업자 목록을 반복·대량으로 조회하려는 요청에는 활용 목적과 법적 근거를 먼저 묻고, 근거가 불명확하면 진행하지 않는다.
|
|
23
|
+
4. **결과 보관은 최소화한다.** 리포트는 요청 시점 참고용으로 한정하고 장기 저장·재배포하지 않는다. 개인사업자 결과에는 대표자명·체납액 등 공개 범위 내 개인정보가 포함될 수 있음을 안내한다.
|
|
24
|
+
|
|
18
25
|
## Design principles
|
|
19
26
|
|
|
20
27
|
- **점수·등급·"위험" 같은 해석 라벨을 산출하지 않는다.** 각 항목의 사실 + 출처 + 조회시각만 병렬한다. 판단은 사용자 몫이다.
|
|
@@ -1,219 +1,90 @@
|
|
|
1
1
|
# Coupang Product Search
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`k-skill-proxy`의 공식 Coupang Partners API route로 쿠팡 상품을 검색한다.
|
|
6
|
+
사용자에게 쿠팡 파트너스 access/secret key를 요구하지 않는다. 키와 HMAC
|
|
7
|
+
서명은 proxy 서버에만 존재한다.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- 로켓배송 전용 필터 검색
|
|
9
|
-
- 가격대 범위 검색
|
|
10
|
-
- 상품 비교표 생성
|
|
11
|
-
- 카테고리별 베스트 상품
|
|
12
|
-
- 골드박스 당일 특가
|
|
13
|
-
- 인기 검색어/계절 상품 추천
|
|
9
|
+
## Endpoint
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
Claude Code / Codex
|
|
19
|
-
→ coupang-product-search/scripts/coupang_partners_mcp.py
|
|
20
|
-
→ git clone/update retention-corp/coupang_partners (user cache)
|
|
21
|
-
→ python3 bin/coupang_mcp.py
|
|
22
|
-
→ local://coupang-mcp compatible tool layer
|
|
23
|
-
├─ Coupang Partners API client (operator keys present)
|
|
24
|
-
└─ hosted fallback → https://a.retn.kr/v1/public/assist (no keys)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Hard rules:
|
|
28
|
-
|
|
29
|
-
- `COUPANG_MCP_ENDPOINT`는 호환성 knob로만 유지한다. 기본값은 `local://coupang-mcp`다.
|
|
30
|
-
- 구형 HF Space hosted MCP 엔드포인트를 사용하거나 새로 지어내지 않는다.
|
|
31
|
-
- upstream 저장소는 `https://github.com/retention-corp/coupang_partners.git`만 사용한다.
|
|
32
|
-
- `tools`와 `init`은 로컬 MCP 계약 확인용으로 먼저 실행한다.
|
|
33
|
-
|
|
34
|
-
## Execution paths
|
|
35
|
-
|
|
36
|
-
`retention-corp/coupang_partners`는 하나의 CLI 뒤에서 두 가지 경로를 자동으로 선택한다. 래퍼(`coupang_partners_mcp.py`)는 두 경로 모두를 그대로 통과시킨다.
|
|
37
|
-
|
|
38
|
-
1. **Operator (local HMAC) path** — `COUPANG_ACCESS_KEY`와 `COUPANG_SECRET_KEY`가 둘 다 설정된 경우. upstream이 Coupang Partners API를 HMAC 서명해 직접 호출한다. 키/시크릿은 절대 답변·문서·커밋에 노출하지 않는다.
|
|
39
|
-
2. **Credentialless hosted fallback path** — 위 두 키 중 하나라도 없는 경우(또는 `OPENCLAW_SHOPPING_FORCE_HOSTED=1`). upstream이 자동으로 Retention Corp의 hosted 백엔드(`https://a.retn.kr/v1/public/assist`)로 떨어진다. 이 경로는 `X-OpenClaw-Client-Id` allowlist로 게이트되어 있으며, upstream이 기본으로 실어 보내는 `openclaw-skill` 값이 현재 Retention Corp allowlist에 등록된 값이다. k-skill 래퍼는 `OPENCLAW_SHOPPING_CLIENT_ID`를 별도로 설정하지 않고 이 upstream 기본값을 그대로 사용한다.
|
|
40
|
-
|
|
41
|
-
두 경로 모두 JSON envelope(`ok`/`data.session_id`/`data.tool`/`data.payload`/`data.result`) 모양은 동일하므로, 답변 로직은 경로를 구별할 필요가 없다. short deeplink는 hosted fallback에서는 `https://a.retn.kr/s/...` 형태로, operator path에서는 `https://link.coupang.com/...` 형태로 온다.
|
|
42
|
-
|
|
43
|
-
### 관련 환경변수
|
|
44
|
-
|
|
45
|
-
| 환경변수 | 역할 | 기본값 |
|
|
46
|
-
|---------|------|--------|
|
|
47
|
-
| `COUPANG_ACCESS_KEY`, `COUPANG_SECRET_KEY` | 운영자 Coupang Partners API 크리덴셜. 둘 다 있을 때만 로컬 HMAC 경로가 활성화된다. | 없음 (없으면 hosted fallback) |
|
|
48
|
-
| `OPENCLAW_SHOPPING_CLIENT_ID` | hosted fallback이 보낼 `X-OpenClaw-Client-Id`. upstream이 `openclaw-skill`을 기본으로 실어 보내며 이 값이 현재 Retention Corp allowlist에 등록되어 있다. k-skill 래퍼는 이 변수를 오버라이드하지 않는 것을 권장한다. | `openclaw-skill` |
|
|
49
|
-
| `OPENCLAW_SHOPPING_FORCE_HOSTED` | `1`이면 키가 있어도 hosted 경로를 강제한다. | 비어있음 |
|
|
50
|
-
| `OPENCLAW_SHOPPING_BASE_URL` | hosted 백엔드 base URL 오버라이드. 스테이징/로컬 backend 테스트용. | `https://a.retn.kr` |
|
|
51
|
-
|
|
52
|
-
## MCP endpoint / contract
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
local://coupang-mcp
|
|
11
|
+
```text
|
|
12
|
+
GET ${KSKILL_PROXY_BASE_URL:-https://k-skill-proxy.nomadamas.org}/v1/coupang/products/search
|
|
56
13
|
```
|
|
57
14
|
|
|
58
|
-
|
|
15
|
+
허용 query:
|
|
59
16
|
|
|
60
|
-
|
|
17
|
+
| field | required | rule |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `keyword` 또는 `q` | yes | 2~100자 검색어 |
|
|
20
|
+
| `limit` | no | 1~10, 기본 10 |
|
|
21
|
+
| `subId` | no | 호출 분석용 식별자, 최대 100자 |
|
|
61
22
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- "20만원 이하 키보드 추천해줘"
|
|
65
|
-
- "아이패드 vs 갤럭시탭 비교"
|
|
66
|
-
- "오늘 쿠팡 특가 뭐 있어?"
|
|
67
|
-
- "전자제품 베스트 보여줘"
|
|
23
|
+
`COUPANG_ACCESS_KEY`와 `COUPANG_SECRET_KEY`를 caller 환경이나 명령 인자로
|
|
24
|
+
넣지 않는다. 운영자가 proxy의 gpu01 runtime `.env`에만 설정한다.
|
|
68
25
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- 돌쇠가 아니며 로그인, 장바구니, 결제 자동화가 필요한 경우
|
|
72
|
-
- 돌쇠가 아니며 쿠팡 계정/session 접근이 필요한 경우
|
|
73
|
-
- 실시간 재고/품절 여부를 100% 보장해야 하는 경우 (hosted fallback과 Partners API 모두 캐시·지연이 있을 수 있다)
|
|
26
|
+
상품 링크를 안내할 때는 반드시 "쿠팡 파트너스 활동을 통해 일정액의 수수료를
|
|
27
|
+
제공받을 수 있습니다."라고 고지한다.
|
|
74
28
|
|
|
75
29
|
## Workflow
|
|
76
30
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
검색어가 너무 넓으면 먼저 의도를 좁힌다.
|
|
80
|
-
|
|
81
|
-
- 권장 질문: `어떤 용도/예산/브랜드/용량을 우선할까요?`
|
|
82
|
-
|
|
83
|
-
### 2. Bootstrap and check the tool contract
|
|
84
|
-
|
|
85
|
-
래퍼는 기본적으로 `~/.cache/k-skill/coupang_partners`에 upstream 저장소를 clone한다. 이미 clone되어 있으면 그대로 사용하고, 최신화가 필요할 때만 `--update`를 붙인다.
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
npx -y @nomadamas/k-skill@0 exec coupang-product-search scripts/coupang_partners_mcp.py -- tools
|
|
89
|
-
npx -y @nomadamas/k-skill@0 exec coupang-product-search scripts/coupang_partners_mcp.py -- init
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
기존 checkout을 명시하거나 CI/검증에서 네트워크 clone을 막으려면:
|
|
31
|
+
1. 검색어가 넓으면 용도, 예산, 브랜드, 용량을 확인한다.
|
|
32
|
+
2. 아래처럼 proxy를 호출한다.
|
|
93
33
|
|
|
94
34
|
```bash
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
--
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
--repo-dir /path/to/coupang_partners \
|
|
101
|
-
--no-clone \
|
|
102
|
-
init
|
|
35
|
+
BASE="${KSKILL_PROXY_BASE_URL:-https://k-skill-proxy.nomadamas.org}"
|
|
36
|
+
curl -fsS --get "${BASE}/v1/coupang/products/search" \
|
|
37
|
+
--data-urlencode 'keyword=무선청소기' \
|
|
38
|
+
--data-urlencode 'limit=10' \
|
|
39
|
+
--data-urlencode 'subId=k-skill'
|
|
103
40
|
```
|
|
104
41
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
# 일반 검색 (키 없이도 hosted fallback으로 작동)
|
|
111
|
-
npx -y @nomadamas/k-skill@0 exec coupang-product-search scripts/coupang_partners_mcp.py -- search "32인치 4K 모니터"
|
|
112
|
-
|
|
113
|
-
# 로켓배송 필터
|
|
114
|
-
npx -y @nomadamas/k-skill@0 exec coupang-product-search scripts/coupang_partners_mcp.py -- rocket "에어팟"
|
|
42
|
+
3. 응답의 `items`를 읽고 `is_rocket`에 따라 로켓배송과 일반배송으로 나눈다.
|
|
43
|
+
4. 사용자의 예산이 있으면 `price`로 필터링하고 상위 3~5개만 비교한다.
|
|
44
|
+
5. 가격, 품절, 배송 정보는 변할 수 있음을 명시한다.
|
|
45
|
+
6. 상품 링크를 제공할 때 아래 affiliate 고지를 반드시 포함한다.
|
|
115
46
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# 비교
|
|
120
|
-
npx -y @nomadamas/k-skill@0 exec coupang-product-search scripts/coupang_partners_mcp.py -- compare "아이패드 vs 갤럭시탭"
|
|
121
|
-
|
|
122
|
-
# 골드박스 (운영자 키가 필요한 upstream 경로)
|
|
123
|
-
npx -y @nomadamas/k-skill@0 exec coupang-product-search scripts/coupang_partners_mcp.py -- goldbox
|
|
47
|
+
```text
|
|
48
|
+
쿠팡 파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있습니다.
|
|
124
49
|
```
|
|
125
50
|
|
|
126
|
-
|
|
51
|
+
## Response fields
|
|
127
52
|
|
|
128
|
-
|
|
53
|
+
각 상품은 다음 안정 필드를 제공한다.
|
|
129
54
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
55
|
+
- `product_id`
|
|
56
|
+
- `title`
|
|
57
|
+
- `price`, `price_text`
|
|
58
|
+
- `url`, `image_url`
|
|
59
|
+
- `review_count`, `score`
|
|
60
|
+
- `is_rocket`, `is_free_shipping`
|
|
134
61
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
사용자가 구매나 장바구니 담기를 요청했고 CloakBrowser mode라면 MCP 결과 링크에서 멈추지 않는다.
|
|
138
|
-
|
|
139
|
-
1. 선택한 상품의 공식 쿠팡 URL을 CloakBrowser로 연다.
|
|
140
|
-
2. 현재 상품명, 판매자, 옵션, 수량, 로켓배송 여부, 실시간 가격과 품절 여부를 다시 확인한다.
|
|
141
|
-
3. 로그인이 필요하면 provisioned vault capability를 사용하고, 없으면 `request_vault_credential`로 쿠팡 login을 저장한 뒤 같은 turn에 재개한다.
|
|
142
|
-
4. 옵션과 수량을 선택해 장바구니에 담는다. 장바구니 담기는 가역적이므로 별도 승인 없이 수행하고 실제 담김 상태를 확인한다.
|
|
143
|
-
5. 구매 요청이면 배송지/쿠폰/결제수단 적용 후 최종 주문 직전까지 진행한다.
|
|
144
|
-
6. `clarify`로 상품·옵션·수량·배송지 요약·최종 금액을 승인받고, 승인되면 주문/결제를 실행해 주문번호와 상태를 확인한다.
|
|
145
|
-
|
|
146
|
-
CloakBrowser를 사용할 수 없는 generic runtime에서는 기존 검색/링크 제공 범위를 유지한다.
|
|
147
|
-
|
|
148
|
-
## Available tools
|
|
149
|
-
|
|
150
|
-
| 도구명 | CLI 명령 | 기능 | 파라미터 예시 |
|
|
151
|
-
|--------|----------|------|-------------|
|
|
152
|
-
| `search_coupang_products` | `search` | 일반 상품 검색 | `"생수"` |
|
|
153
|
-
| `search_coupang_rocket` | `rocket` | 로켓배송만 필터링 | `"에어팟"` |
|
|
154
|
-
| `search_coupang_budget` | `budget` | 가격대 범위 검색 | `"키보드" --max-price 100000` |
|
|
155
|
-
| `compare_coupang_products` | `compare` | 상품 비교표 생성 | `"아이패드 vs 갤럭시탭"` |
|
|
156
|
-
| `get_coupang_recommendations` | `recommendations` | 인기 검색어 제안 | `--category 전자제품` |
|
|
157
|
-
| `get_coupang_seasonal` | `seasonal` | 계절/상황별 추천 | `"설날 선물"` |
|
|
158
|
-
| `get_coupang_best_products` | `best` | 카테고리별 베스트 | `--category-id 1016` |
|
|
159
|
-
| `get_coupang_goldbox` | `goldbox` | 당일 특가 정보 | `--limit 10` |
|
|
160
|
-
|
|
161
|
-
주의: `get_coupang_goldbox`와 `get_coupang_best_products`는 upstream 기준 Coupang Partners API 권한이 필요한 경로이므로, 키가 없는 환경에서는 실패할 수 있다. 이런 경우 에러 메시지를 그대로 전달하고 hosted fallback이 커버하는 `search`/`rocket`/`budget`/`compare` 경로로 우회 제안한다.
|
|
162
|
-
|
|
163
|
-
## Response format
|
|
164
|
-
|
|
165
|
-
upstream CLI는 JSON을 출력한다. `data.result` 안의 상품 배열 또는 도구별 객체를 읽고, 답변에서는 로켓배송(rocket)과 일반배송(normal)을 구분한다.
|
|
166
|
-
|
|
167
|
-
```json
|
|
168
|
-
{
|
|
169
|
-
"ok": true,
|
|
170
|
-
"data": {
|
|
171
|
-
"session_id": "session-...",
|
|
172
|
-
"tool": "search_coupang_products",
|
|
173
|
-
"payload": {
|
|
174
|
-
"jsonrpc": "2.0",
|
|
175
|
-
"result": {
|
|
176
|
-
"content": [
|
|
177
|
-
{"type": "text", "text": "[...]"}
|
|
178
|
-
]
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
"result": []
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
```
|
|
62
|
+
## Failure modes
|
|
185
63
|
|
|
186
|
-
|
|
64
|
+
- `400 bad_request`: 검색어가 없거나 너무 짧음. 입력을 바로잡아 재호출한다.
|
|
65
|
+
- `503 upstream_not_configured`: proxy 운영자가 Coupang key 두 개를 아직 설정하지 않음.
|
|
66
|
+
- `502 upstream_forbidden`: 키가 거절되었거나 Partners API 권한이 없음.
|
|
67
|
+
- `502 upstream_error` / `upstream_unavailable`: 쿠팡 upstream 장애. 실패를 숨기지 말고 나중 재시도를 안내한다.
|
|
187
68
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
1) LG전자 4K UHD 모니터
|
|
192
|
-
가격: 397,750원 (참고용)
|
|
193
|
-
보러가기: https://a.retn.kr/s/... # hosted fallback shortlink
|
|
194
|
-
또는: https://link.coupang.com/a/... # operator HMAC 경로 딥링크
|
|
69
|
+
임의의 Coupang scraping, 구형 HF Space MCP, `a.retn.kr` hosted fallback,
|
|
70
|
+
사용자 제공 API key로 우회하지 않는다.
|
|
195
71
|
|
|
196
|
-
##
|
|
197
|
-
|
|
198
|
-
1) 삼성전자 QHD 오디세이 G5 게이밍 모니터
|
|
199
|
-
가격: 283,000원 (참고용)
|
|
200
|
-
보러가기: https://a.retn.kr/s/...
|
|
201
|
-
```
|
|
72
|
+
## Continue to cart or purchase
|
|
202
73
|
|
|
203
|
-
|
|
74
|
+
사용자가 장바구니 또는 구매를 요청했고 CloakBrowser mode라면 결과 링크에서
|
|
75
|
+
멈추지 않는다.
|
|
204
76
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
77
|
+
1. 선택 상품의 공식 쿠팡 URL을 열어 상품명, 판매자, 옵션, 수량, 실시간 가격,
|
|
78
|
+
로켓배송 여부와 품절 상태를 다시 확인한다.
|
|
79
|
+
2. 로그인이 필요하면 provisioned vault capability를 사용하고, 없으면
|
|
80
|
+
`request_vault_credential`로 쿠팡 login을 저장한 뒤 재개한다.
|
|
81
|
+
3. 장바구니 담기는 가역적이므로 수행 후 실제 담김을 확인한다.
|
|
82
|
+
4. 구매는 배송지, 쿠폰, 결제수단 적용 후 최종 주문 직전에 정확한 대상과 금액을
|
|
83
|
+
`clarify`로 승인받고 실행한다.
|
|
210
84
|
|
|
211
85
|
## Done when
|
|
212
86
|
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
- affiliate 고지(disclosure)가 답변에 포함되었다.
|
|
218
|
-
- 돌쇠의 장바구니 요청이면 선택 옵션/수량이 실제 장바구니에 담긴 것을 확인했다.
|
|
219
|
-
- 돌쇠의 구매 요청이면 `clarify` 승인 후 주문번호와 결제 상태를 확인했다.
|
|
87
|
+
- proxy 응답을 실제로 받았다.
|
|
88
|
+
- 로켓배송/일반배송과 가격을 구분해 후보를 정리했다.
|
|
89
|
+
- 가격·배송 변동 가능성과 affiliate 고지를 포함했다.
|
|
90
|
+
- 액션 요청이면 해당 표면의 실제 완료 상태를 확인했다.
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coupang-product-search",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "k-skill-proxy의 공식 Coupang Partners API 경로로 쿠팡 상품을 검색하고 로켓배송·가격 후보를 비교한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
|
|
4
4
|
"profiles": [
|
|
5
5
|
"vault",
|
|
6
6
|
"browser",
|
|
7
7
|
"action:commerce"
|
|
8
8
|
],
|
|
9
|
-
"
|
|
9
|
+
"stub_notice": {
|
|
10
|
+
"heading": "Affiliate disclosure (required)",
|
|
11
|
+
"body": "상품 링크를 안내할 때는 반드시 \"쿠팡 파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있습니다.\"라고 고지한다."
|
|
12
|
+
},
|
|
13
|
+
"frontmatter": "name: coupang-product-search\ndescription: k-skill-proxy의 공식 Coupang Partners API 경로로 쿠팡 상품을 검색하고 로켓배송·가격 후보를 비교한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: retail\n locale: ko-KR\n phase: v2"
|
|
10
14
|
}
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
이 스킬은 참고용 작성 보조다. 법률 자문, 승소 가능성 판단, 관할 확정, 최종 제출 대행을 하지 않는다.
|
|
8
8
|
|
|
9
|
+
## 법적 안전 운영 기준
|
|
10
|
+
|
|
11
|
+
이 스킬은 변호사법상 무면허 법률사무 취급에 해당하지 않도록 아래 기준 안에서만 동작한다.
|
|
12
|
+
|
|
13
|
+
1. **본인 사건 전용이다.** 사용자가 자신의 채권 회수를 위해 쓰는 참고 도구로 한정하고, 제3자의 소송·지급명령 문서를 대가를 받고 대행 작성·중개·알선하지 않는다. 그런 요청이 오면 변호사/법무사 상담을 안내한다.
|
|
14
|
+
2. **산출물은 항상 참고용 초안이다.** 신청서 초안·체크리스트에는 법적 효력을 보장하지 않는 참고용임을 표기하고, 제출 전 변호사·법무사 또는 법원 나홀로소송 안내 확인을 사용자에게 고정 안내한다.
|
|
15
|
+
3. **법률 판단을 하지 않는다.** 승소 가능성, 관할 확정, 청구원인의 법적 유효성, 소송 전략은 판단하지 않는다. 관할 법원은 사용자가 최종 확인한다.
|
|
16
|
+
4. **최종 제출은 사용자가 직접 한다.** 전자소송 로그인·인증·전자서명·결제·제출은 사용자 본인의 공식 포털 세션에서만 진행하고, 스킬은 입력값 handoff까지만 준비한다.
|
|
17
|
+
|
|
9
18
|
## When to use
|
|
10
19
|
|
|
11
20
|
- "떼인 돈 지급명령 신청 준비해줘"
|
|
@@ -26,7 +26,16 @@
|
|
|
26
26
|
|
|
27
27
|
- 인터넷 연결
|
|
28
28
|
- `node` 18+
|
|
29
|
-
-
|
|
29
|
+
- `daiso-product-search` npm package
|
|
30
|
+
|
|
31
|
+
설치:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install daiso-product-search
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
이 저장소에서 개발할 때는 루트에서 `npm install` 후 `packages/daiso-product-search`를 쓴다.
|
|
38
|
+
package 설치가 막히면 표면을 직접 호출해 우회하지 말고 설치를 먼저 해결한다. 직접 호출은 실패가 성공처럼 보이는 응답을 준다(Failure modes 참고).
|
|
30
39
|
|
|
31
40
|
## Required inputs
|
|
32
41
|
|
|
@@ -161,6 +170,7 @@ console.log(result.pickupStock)
|
|
|
161
170
|
- 현재 확인된 공식 표면은 **매장 내 aisle/진열 위치**를 직접 주지 않을 수 있다.
|
|
162
171
|
- `selStrPkupStck` 403 → `/api/auth/request` 재호출 후 Bearer를 새로 빌드해 재시도한다.
|
|
163
172
|
- Bearer 재시도 후에도 401/403이면 재고 수량은 `retrievalStatus: "blocked"` 로 표시하고, `selPkupStr` 기반 `pickupEligibility`(픽업 가능 여부)만 보조 정보로 제공한다.
|
|
173
|
+
- **package 없이 표면을 직접 호출하면 조용히 틀린다.** `SearchGoods`는 검색어 파라미터명이 틀려도 400이 아니라 200에 전체 카탈로그를 돌려준다. 검색어는 JSON 바디가 아니라 쿼리스트링 `searchTerm`으로 보내야 하고, `selStr`도 바디 키가 틀리면 무관한 매장 1건만 준다. 둘 다 실패가 성공처럼 보이므로 반드시 package를 경유한다.
|
|
164
174
|
|
|
165
175
|
## Notes
|
|
166
176
|
|
|
@@ -48,7 +48,8 @@ npx -y @nomadamas/k-skill@0 exec fsc-corporate-info scripts/fsc_corporate_info.p
|
|
|
48
48
|
- `400 bad_request`: 법인명을 주지 않음.
|
|
49
49
|
- `503 upstream_not_configured`: 프록시 서버에 `DATA_GO_KR_API_KEY` 없음.
|
|
50
50
|
- `502 upstream_forbidden`: 프록시 키가 15043184에 활용신청되지 않음.
|
|
51
|
-
-
|
|
51
|
+
- `coverage`: 기업기본정보 데이터셋 범위, 법인명 후보 및 선택적 사업자번호 교차검증 기준, 제외 범위, 0건의 의미, 조회시각(`checked_at`)을 구조화해 제공한다.
|
|
52
|
+
- 빈 결과: 이 데이터셋에서 입력 법인명 후보가 없음. 법인이 존재하지 않는다는 뜻이 아니며 표기 차이 가능성이 있다.
|
|
52
53
|
|
|
53
54
|
## Official surfaces
|
|
54
55
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import fsc_corporate_info as subject
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CoveragePassthroughTest(unittest.TestCase):
|
|
7
|
+
def test_zero_result_keeps_proxy_coverage(self):
|
|
8
|
+
payload = {
|
|
9
|
+
"candidate_count": 0,
|
|
10
|
+
"candidates": [],
|
|
11
|
+
"coverage": {"scope": "fsc-corporate-outline-dataset"},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
response = subject.query_corp_outline("없는법인", read_json=lambda _request: payload)
|
|
15
|
+
|
|
16
|
+
self.assertIs(response, payload)
|
|
17
|
+
self.assertEqual(response["coverage"]["scope"], "fsc-corporate-outline-dataset")
|
|
18
|
+
|
|
19
|
+
def test_matched_result_keeps_proxy_coverage(self):
|
|
20
|
+
payload = {
|
|
21
|
+
"candidate_count": 1,
|
|
22
|
+
"candidates": [{"corpNm": "테스트"}],
|
|
23
|
+
"coverage": {
|
|
24
|
+
"match_basis": "corporate-name-candidates-with-optional-business-number-cross-check"
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
response = subject.query_corp_outline(
|
|
29
|
+
"테스트",
|
|
30
|
+
"123-45-67890",
|
|
31
|
+
read_json=lambda _request: payload,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
self.assertIs(response, payload)
|
|
35
|
+
self.assertEqual(
|
|
36
|
+
response["coverage"]["match_basis"],
|
|
37
|
+
"corporate-name-candidates-with-optional-business-number-cross-check",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
unittest.main()
|
|
@@ -51,7 +51,8 @@ npx -y @nomadamas/k-skill@0 exec g2b-sanctioned-supplier scripts/g2b_sanctioned_
|
|
|
51
51
|
- `400 bad_request`: 사업자번호가 10자리가 아님.
|
|
52
52
|
- `503 upstream_not_configured`: 프록시 서버에 `DATA_GO_KR_API_KEY` 없음.
|
|
53
53
|
- `502 upstream_forbidden`: 프록시 키가 15129466에 활용신청되지 않음.
|
|
54
|
-
- `
|
|
54
|
+
- `coverage`: 현재 유효 제재 범위, 사업자번호 정확 일치 기준, 과거·미등록 제외 범위, 0건의 의미, 조회시각(`checked_at`)을 구조화해 제공한다.
|
|
55
|
+
- `total_count = 0`: 조회시점 현재 유효한 제재가 조회되지 않음. 만료·해제된 과거 제재가 없다는 뜻은 아니다.
|
|
55
56
|
|
|
56
57
|
## Official surfaces
|
|
57
58
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import g2b_sanctioned_supplier as subject
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CoveragePassthroughTest(unittest.TestCase):
|
|
7
|
+
def test_zero_result_keeps_proxy_coverage(self):
|
|
8
|
+
payload = {
|
|
9
|
+
"total_count": 0,
|
|
10
|
+
"active_sanctions": [],
|
|
11
|
+
"coverage": {"scope": "currently-effective-g2b-sanctions"},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
response = subject.query_sanctions("123-45-67890", read_json=lambda _request: payload)
|
|
15
|
+
|
|
16
|
+
self.assertIs(response, payload)
|
|
17
|
+
self.assertEqual(response["coverage"]["scope"], "currently-effective-g2b-sanctions")
|
|
18
|
+
|
|
19
|
+
def test_matched_result_keeps_proxy_coverage(self):
|
|
20
|
+
payload = {
|
|
21
|
+
"total_count": 1,
|
|
22
|
+
"active_sanctions": [{"bizno": "1234567890"}],
|
|
23
|
+
"coverage": {"match_basis": "exact-business-number"},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
response = subject.query_sanctions("1234567890", read_json=lambda _request: payload)
|
|
27
|
+
|
|
28
|
+
self.assertIs(response, payload)
|
|
29
|
+
self.assertEqual(response["coverage"]["match_basis"], "exact-business-number")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
unittest.main()
|
|
@@ -27,7 +27,15 @@
|
|
|
27
27
|
|
|
28
28
|
- 인터넷 연결
|
|
29
29
|
- `node` 18+
|
|
30
|
-
-
|
|
30
|
+
- `market-kurly-search` npm package
|
|
31
|
+
|
|
32
|
+
설치:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install market-kurly-search
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
이 저장소에서 개발할 때는 루트에서 `npm install` 후 `packages/market-kurly-search`를 쓴다.
|
|
31
39
|
|
|
32
40
|
## Required inputs
|
|
33
41
|
|
|
@@ -43,6 +43,12 @@
|
|
|
43
43
|
- 국민연금 데이터는 사업자번호 앞 6자리만 공개되므로, 6자리 일치 + 상호 유사 후보를 나열할 뿐 사업장 동일성을 단정하지 않는다.
|
|
44
44
|
- 공개 범위는 법인·근로자 일정 규모 이상 사업장 위주이며, 소규모/개인 사업장은 미공개일 수 있다.
|
|
45
45
|
|
|
46
|
+
## 법적 안전 운영 기준
|
|
47
|
+
|
|
48
|
+
1. **공단이 공식 공개한 사업장 정보만 조회한다.** 가입자수·고지금액은 국민연금공단이 공공데이터포털로 공개한 사업장 단위 정보이며, 개별 근로자의 개인정보는 조회·추정하지 않는다.
|
|
49
|
+
2. **조회 목적을 명시한다.** 거래처 실사, 입사 검토, 시장 조사 같은 정당한 확인 목적의 개별 조회로 한정하고, 특정 사업장·업종의 대량·정기 수집에는 목적과 법적 근거를 먼저 확인한다. 근거가 불명확하면 진행하지 않는다.
|
|
50
|
+
3. **결과 저장은 최소화한다.** 조회 결과는 요청 시점 참고용으로 한정하고 장기 저장·재배포하지 않는다.
|
|
51
|
+
|
|
46
52
|
## CLI examples
|
|
47
53
|
|
|
48
54
|
```bash
|
|
@@ -47,7 +47,8 @@ npx -y @nomadamas/k-skill@0 exec nts-tax-delinquency scripts/nts_tax_delinquency
|
|
|
47
47
|
## Failure modes
|
|
48
48
|
|
|
49
49
|
- `unavailable` + 안내: 상호 미입력, 네트워크 오류, 페이지 구조 변경 추정 — 수동 확인 URL 제공.
|
|
50
|
-
-
|
|
50
|
+
- `coverage`: 조회한 공개 명단 범위, 상호·법인명 문자열 대조 기준, 제외 범위, 0건의 의미, 조회시각(`checked_at`)을 구조화해 제공한다.
|
|
51
|
+
- 0건: 두 명단 모두 공개 명단에서 문자열 매치가 없음 (`match_count: 0`). 모든 국세 체납이 없다는 뜻은 아니다.
|
|
51
52
|
|
|
52
53
|
## Official surfaces
|
|
53
54
|
|
|
@@ -36,6 +36,18 @@ INDIV_COLUMNS = ("no", "공개년도", "성명", "연령", "상호", "직업(업
|
|
|
36
36
|
IDENTITY_NOTE = ("명단공개 자료에는 사업자등록번호가 수록되지 않아 입력 사업자번호와의 "
|
|
37
37
|
"동일성은 확인할 수 없다 — 상호·법인명 문자열 일치 후보의 공개 사실만 "
|
|
38
38
|
"나열하며, 동명 상호일 가능성은 사용자가 판단한다.")
|
|
39
|
+
COVERAGE = {
|
|
40
|
+
"scope": "nts-high-amount-habitual-delinquent-disclosure",
|
|
41
|
+
"match_basis": "corporate-name-and-trade-name-string-match",
|
|
42
|
+
"exclusions": [
|
|
43
|
+
"명단공개 기준에 들지 않는 체납 및 비공개 체납",
|
|
44
|
+
"사업자등록번호 동일성 확인",
|
|
45
|
+
],
|
|
46
|
+
"zero_result_meaning": (
|
|
47
|
+
"조회한 국세청 고액·상습체납자 공개 명단에서 법인명·상호 문자열 일치가 없다는 뜻이며, "
|
|
48
|
+
"모든 국세 체납이 없다는 뜻은 아니다."
|
|
49
|
+
),
|
|
50
|
+
}
|
|
39
51
|
|
|
40
52
|
_HEADING_MARKER = "고액상습체납자"
|
|
41
53
|
_ZERO_MARKER = "조회된 데이터가 없습니다"
|
|
@@ -50,13 +62,15 @@ def _now_iso() -> str:
|
|
|
50
62
|
|
|
51
63
|
|
|
52
64
|
def _envelope(status: str, *, result: dict | None = None, note: str | None = None) -> dict:
|
|
65
|
+
looked_up_at = _now_iso()
|
|
53
66
|
return {
|
|
54
67
|
"source": SOURCE,
|
|
55
|
-
"looked_up_at":
|
|
68
|
+
"looked_up_at": looked_up_at,
|
|
56
69
|
"status": status,
|
|
57
70
|
"result": result,
|
|
58
71
|
"origin": "unauthenticated-public",
|
|
59
72
|
"note": note,
|
|
73
|
+
"coverage": {**COVERAGE, "checked_at": looked_up_at} if status == "ok" else None,
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import nts_tax_delinquency as subject
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CoverageTest(unittest.TestCase):
|
|
8
|
+
def test_zero_result_explains_disclosure_scope(self):
|
|
9
|
+
with patch.object(subject, "_search", side_effect=[[], []]):
|
|
10
|
+
response = subject.lookup("테스트상사")
|
|
11
|
+
|
|
12
|
+
self.assertEqual(response["status"], "ok")
|
|
13
|
+
self.assertEqual(response["result"]["corporate_list"]["match_count"], 0)
|
|
14
|
+
self.assertEqual(response["result"]["individual_list"]["match_count"], 0)
|
|
15
|
+
self.assertEqual(response["coverage"]["match_basis"], "corporate-name-and-trade-name-string-match")
|
|
16
|
+
self.assertTrue(response["coverage"]["zero_result_meaning"])
|
|
17
|
+
self.assertTrue(response["coverage"]["checked_at"])
|
|
18
|
+
|
|
19
|
+
def test_matched_result_keeps_same_coverage_contract(self):
|
|
20
|
+
corporate = [{"법인명": "테스트상사", "총체납액": "1억원"}]
|
|
21
|
+
individual = [{"상호": "테스트상사", "총체납액": "2억원"}]
|
|
22
|
+
with patch.object(subject, "_search", side_effect=[corporate, individual]):
|
|
23
|
+
response = subject.lookup("테스트상사")
|
|
24
|
+
|
|
25
|
+
self.assertEqual(response["result"]["corporate_list"]["matches"], corporate)
|
|
26
|
+
self.assertEqual(response["result"]["individual_list"]["matches"], individual)
|
|
27
|
+
self.assertEqual(response["coverage"]["scope"], "nts-high-amount-habitual-delinquent-disclosure")
|
|
28
|
+
self.assertIn("사업자등록번호 동일성 확인", response["coverage"]["exclusions"])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
unittest.main()
|
package/skills/store-longevity-radar/scripts/__pycache__/store_longevity_download.cpython-312.pyc
CHANGED
|
Binary file
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Bootstrap and run retention-corp/coupang_partners Coupang MCP tools.
|
|
3
|
-
|
|
4
|
-
The k-skill repo intentionally does not vendor the third-party implementation.
|
|
5
|
-
This wrapper keeps the skill pointed at the approved upstream repository, clones it
|
|
6
|
-
into a user cache when needed, and then delegates to its local MCP-compatible CLI.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import argparse
|
|
12
|
-
import os
|
|
13
|
-
import pathlib
|
|
14
|
-
import subprocess
|
|
15
|
-
import sys
|
|
16
|
-
from typing import Sequence
|
|
17
|
-
|
|
18
|
-
UPSTREAM_REPO_URL = "https://github.com/retention-corp/coupang_partners.git"
|
|
19
|
-
DEFAULT_MCP_ENDPOINT = "local://coupang-mcp"
|
|
20
|
-
DEFAULT_REPO_DIR = pathlib.Path(os.getenv("COUPANG_PARTNERS_REPO_DIR", "~/.cache/k-skill/coupang_partners")).expanduser()
|
|
21
|
-
UPSTREAM_CLI = pathlib.Path("bin") / "coupang_mcp.py"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class BootstrapError(RuntimeError):
|
|
25
|
-
"""Raised when the upstream checkout cannot be prepared."""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
29
|
-
parser = argparse.ArgumentParser(
|
|
30
|
-
description="Run the retention-corp/coupang_partners local Coupang MCP-compatible CLI.",
|
|
31
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
32
|
-
epilog=(
|
|
33
|
-
"Examples:\n"
|
|
34
|
-
" coupang_partners_mcp.py tools\n"
|
|
35
|
-
" coupang_partners_mcp.py init\n"
|
|
36
|
-
" coupang_partners_mcp.py search 생수\n"
|
|
37
|
-
" coupang_partners_mcp.py budget 키보드 --max-price 100000\n"
|
|
38
|
-
"\n"
|
|
39
|
-
"Honored upstream environment variables (forwarded as-is):\n"
|
|
40
|
-
" COUPANG_ACCESS_KEY, COUPANG_SECRET_KEY\n"
|
|
41
|
-
" Operator Coupang Partners API credentials. When set, upstream\n"
|
|
42
|
-
" uses the local HMAC-signed Coupang Partners path.\n"
|
|
43
|
-
" OPENCLAW_SHOPPING_CLIENT_ID\n"
|
|
44
|
-
" Allowlisted client id for the hosted fallback. upstream sends\n"
|
|
45
|
-
" openclaw-skill by default, which is the value currently on the\n"
|
|
46
|
-
" Retention Corp allowlist; k-skill does not override this.\n"
|
|
47
|
-
" OPENCLAW_SHOPPING_FORCE_HOSTED=1\n"
|
|
48
|
-
" Force the hosted fallback even when Coupang keys are present.\n"
|
|
49
|
-
" OPENCLAW_SHOPPING_BASE_URL\n"
|
|
50
|
-
" Override the hosted backend base URL. Default upstream target\n"
|
|
51
|
-
" is https://a.retn.kr and /v1/public/assist is the public entry.\n"
|
|
52
|
-
"\n"
|
|
53
|
-
"When both COUPANG_ACCESS_KEY and COUPANG_SECRET_KEY are missing,\n"
|
|
54
|
-
"upstream falls back to the hosted Retention Corp backend so this\n"
|
|
55
|
-
"skill keeps working without Coupang Partners credentials."
|
|
56
|
-
),
|
|
57
|
-
)
|
|
58
|
-
parser.add_argument(
|
|
59
|
-
"--repo-dir",
|
|
60
|
-
default=str(DEFAULT_REPO_DIR),
|
|
61
|
-
help="Checkout directory for retention-corp/coupang_partners (default: %(default)s).",
|
|
62
|
-
)
|
|
63
|
-
parser.add_argument(
|
|
64
|
-
"--no-clone",
|
|
65
|
-
action="store_true",
|
|
66
|
-
help="Do not clone the upstream repository if it is missing; fail with setup guidance instead.",
|
|
67
|
-
)
|
|
68
|
-
parser.add_argument(
|
|
69
|
-
"--update",
|
|
70
|
-
action="store_true",
|
|
71
|
-
help="Run git pull --ff-only in an existing upstream checkout before delegating.",
|
|
72
|
-
)
|
|
73
|
-
parser.add_argument(
|
|
74
|
-
"upstream_args",
|
|
75
|
-
nargs=argparse.REMAINDER,
|
|
76
|
-
help="Arguments passed to bin/coupang_mcp.py, for example: tools, search 생수, rocket 에어팟.",
|
|
77
|
-
)
|
|
78
|
-
args = parser.parse_args(argv)
|
|
79
|
-
if args.upstream_args and args.upstream_args[0] == "--":
|
|
80
|
-
args.upstream_args = args.upstream_args[1:]
|
|
81
|
-
if not args.upstream_args:
|
|
82
|
-
parser.error("missing upstream command; try: tools, init, search <keyword>, rocket <keyword>, budget <keyword>")
|
|
83
|
-
return args
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def upstream_cli_path(repo_dir: pathlib.Path) -> pathlib.Path:
|
|
87
|
-
return repo_dir / UPSTREAM_CLI
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def ensure_repo(repo_dir: pathlib.Path, *, clone: bool = True, update: bool = False) -> pathlib.Path:
|
|
91
|
-
cli_path = upstream_cli_path(repo_dir)
|
|
92
|
-
if cli_path.exists():
|
|
93
|
-
if update:
|
|
94
|
-
run_checked(["git", "-C", str(repo_dir), "pull", "--ff-only"], "failed to update upstream checkout")
|
|
95
|
-
return cli_path
|
|
96
|
-
|
|
97
|
-
if repo_dir.exists():
|
|
98
|
-
raise BootstrapError(
|
|
99
|
-
f"{repo_dir} exists but does not look like retention-corp/coupang_partners "
|
|
100
|
-
f"(missing {UPSTREAM_CLI}). Recreate it with: git clone {UPSTREAM_REPO_URL} {repo_dir}"
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
if not clone:
|
|
104
|
-
raise BootstrapError(
|
|
105
|
-
f"Missing retention-corp/coupang_partners checkout at {repo_dir}. "
|
|
106
|
-
f"Create it with: git clone {UPSTREAM_REPO_URL} {repo_dir}"
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
repo_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
-
run_checked(["git", "clone", "--depth", "1", UPSTREAM_REPO_URL, str(repo_dir)], "failed to clone upstream checkout")
|
|
111
|
-
if not cli_path.exists():
|
|
112
|
-
raise BootstrapError(f"Cloned {UPSTREAM_REPO_URL}, but {UPSTREAM_CLI} was not found in {repo_dir}")
|
|
113
|
-
return cli_path
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def run_checked(command: Sequence[str], context: str) -> None:
|
|
117
|
-
try:
|
|
118
|
-
subprocess.run(command, check=True)
|
|
119
|
-
except FileNotFoundError as exc:
|
|
120
|
-
raise BootstrapError(f"{context}: required executable not found: {command[0]}") from exc
|
|
121
|
-
except subprocess.CalledProcessError as exc:
|
|
122
|
-
raise BootstrapError(f"{context}: {exc}") from exc
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def build_command(cli_path: pathlib.Path, upstream_args: Sequence[str]) -> list[str]:
|
|
126
|
-
return [sys.executable, str(cli_path), *upstream_args]
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def main(argv: Sequence[str] | None = None) -> int:
|
|
130
|
-
args = parse_args(argv)
|
|
131
|
-
repo_dir = pathlib.Path(args.repo_dir).expanduser().resolve()
|
|
132
|
-
|
|
133
|
-
try:
|
|
134
|
-
cli_path = ensure_repo(repo_dir, clone=not args.no_clone, update=args.update)
|
|
135
|
-
except BootstrapError as exc:
|
|
136
|
-
print(f"coupang_partners_mcp.py: {exc}", file=sys.stderr)
|
|
137
|
-
return 2
|
|
138
|
-
|
|
139
|
-
env = os.environ.copy()
|
|
140
|
-
env.setdefault("COUPANG_MCP_ENDPOINT", DEFAULT_MCP_ENDPOINT)
|
|
141
|
-
completed = subprocess.run(build_command(cli_path, args.upstream_args), env=env)
|
|
142
|
-
return int(completed.returncode)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if __name__ == "__main__":
|
|
146
|
-
raise SystemExit(main())
|