@nomadamas/k-skill 0.4.2 → 0.5.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.
- package/package.json +1 -1
- package/skills/animal-pharmacy-search/instruction.md +150 -0
- package/skills/animal-pharmacy-search/scripts/animal_pharmacy_mcp.py +276 -0
- package/skills/animal-pharmacy-search/skill.json +8 -0
- package/skills/consumer-price-safety-search/instruction.md +30 -0
- package/skills/consumer-price-safety-search/skill.json +6 -0
- package/skills/coupang-product-search/instruction.md +59 -188
- package/skills/coupang-product-search/skill.json +6 -2
- 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/government-support-survey/instruction.md +52 -0
- package/skills/government-support-survey/references/NOTICE.md +30 -0
- package/skills/government-support-survey/scripts/run_survey.py +71 -0
- package/skills/government-support-survey/skill.json +10 -0
- package/skills/kamis-food-price/instruction.md +66 -0
- package/skills/kamis-food-price/scripts/run_kamis.py +110 -0
- package/skills/kamis-food-price/skill.json +9 -0
- package/skills/korean-cinema-search/instruction.md +7 -1
- package/skills/market-kurly-search/instruction.md +9 -1
- package/skills/mofa-travel-safety/instruction.md +62 -0
- package/skills/mofa-travel-safety/scripts/run_mofa_travel_safety.py +57 -0
- package/skills/mofa-travel-safety/skill.json +9 -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/seoul-weather-risk/instruction.md +4 -1
- package/skills/seoul-weather-risk/scripts/seoul_weather_risk.py +99 -3
- package/skills/store-longevity-radar/scripts/__pycache__/store_longevity_download.cpython-312.pyc +0 -0
- package/templates/browser.md +1 -1
- package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +0 -146
|
@@ -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
|
}
|
|
@@ -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()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 정부지원 전수조사
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
`k-skill-proxy`의 `/v1/government-support/survey`를 호출해 다음 공개 공고를 하나의 스키마로 조사한다.
|
|
6
|
+
|
|
7
|
+
- K-Startup Open API
|
|
8
|
+
- 기업마당
|
|
9
|
+
- 정보통신산업진흥원(NIPA)
|
|
10
|
+
- 한국콘텐츠진흥원(KOCCA)
|
|
11
|
+
- 중소기업기술개발사업 종합관리시스템(SMTECH)
|
|
12
|
+
|
|
13
|
+
이 스킬은 현재 모집 공고의 제목·분야·주관기관·접수기간·공식 URL을 수집하고, 사용자의 프로젝트 조건과 대조해 `즉시 지원 가능`, `요건 충족 시 가능`, `사업 변형 시 가능`, `부적합`으로 보수적으로 분류한다.
|
|
14
|
+
|
|
15
|
+
## Required workflow
|
|
16
|
+
|
|
17
|
+
1. 회사·팀·프로젝트의 업력, 소재지, 업종, 기술, 대표자 연령, 기업 형태, 매출·투자 단계, 원하는 지원 유형을 확인한다.
|
|
18
|
+
2. 먼저 5개 소스를 모두 조회한다. 사용자가 범위를 좁힌 경우에만 `--sources`를 제한한다.
|
|
19
|
+
3. 응답의 `complete`와 소스별 `ok`를 확인한다. 하나라도 실패하면 “전수조사 완료”라고 표현하지 말고 누락 소스를 명시한다.
|
|
20
|
+
4. 제목만으로 자격을 확정하지 않는다. 후보의 공식 상세 URL과 첨부 공고문에서 신청대상·제외조건·지역·업력·중복수혜 제한을 확인한다.
|
|
21
|
+
5. 결과마다 공식 출처 URL, 접수 마감일, 판정 근거, 추가 확인사항을 표시한다.
|
|
22
|
+
6. 실제 신청은 공식 사이트에서 진행한다. 최종 제출 직전에는 사용자 승인을 받는다.
|
|
23
|
+
|
|
24
|
+
## Command
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx -y @nomadamas/k-skill@0 exec government-support-survey scripts/run_survey.py -- \
|
|
28
|
+
--sources kstartup bizinfo nipa kocca smtech \
|
|
29
|
+
--keyword "AI 바우처" \
|
|
30
|
+
--max-pages 3
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
전체 공고를 넓게 조사할 때는 `--keyword`를 생략한다. 응답이 너무 클 때만 공고 유형별 키워드로 여러 번 나눠 조회한다.
|
|
34
|
+
|
|
35
|
+
## Output contract
|
|
36
|
+
|
|
37
|
+
- `complete`: 요청한 모든 소스가 정상 조사됐는지
|
|
38
|
+
- `sources`: 소스별 성공 여부, 조회 페이지, 수집 건수, 오류
|
|
39
|
+
- `items`: `source`, `id`, `title`, `field`, `org`, `apply_start`, `apply_end`, `reg_date`, `url`
|
|
40
|
+
- `attribution`: upstream 코드와 공식 데이터 출처, 재배포 범위
|
|
41
|
+
|
|
42
|
+
## Failure modes
|
|
43
|
+
|
|
44
|
+
- `complete=false`: 일부 소스 실패. 수집된 결과는 부분 결과이며 누락 소스를 수동 확인한다.
|
|
45
|
+
- `401/403` 또는 차단 신호: 우회하지 말고 공식 브라우저 화면으로 전환한다.
|
|
46
|
+
- `0 items parsed; site layout may have changed`: 파서 개편 가능성이므로 해당 소스를 성공 처리하지 않는다.
|
|
47
|
+
- K-Startup 키 미설정: 나머지 공개 포털 결과는 유지하되 K-Startup 누락을 명시한다.
|
|
48
|
+
- CAPTCHA·로그인·본인인증: 자동 우회하지 않는다.
|
|
49
|
+
|
|
50
|
+
## Legal and redistribution boundary
|
|
51
|
+
|
|
52
|
+
이 스킬의 조사 로직은 `djfksjd/ir-search`의 MIT 라이선스 구현을 참고해 재작성했으며 저작권·라이선스 고지는 `npx -y @nomadamas/k-skill@0 read government-support-survey references/NOTICE.md`에 보존한다. 정부 포털 공고 원문과 첨부는 포털별 이용조건이 다르므로 k-skill은 이를 미러링하거나 재배포하지 않고 구조화 메타데이터와 공식 링크만 제공한다.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 출처, 라이선스, 재배포 범위
|
|
2
|
+
|
|
3
|
+
## 참고한 오픈소스
|
|
4
|
+
|
|
5
|
+
- 프로젝트: [djfksjd/ir-search](https://github.com/djfksjd/ir-search)
|
|
6
|
+
- 라이선스: [MIT License](https://github.com/djfksjd/ir-search/blob/main/LICENSE)
|
|
7
|
+
- 원 저작권 표시: `Copyright (c) 2026 ir-search contributors`
|
|
8
|
+
|
|
9
|
+
MIT 라이선스는 사용·수정·병합·배포·재라이선스를 허용하며, 소프트웨어의 복제물 또는 중요한 부분에 저작권 고지와 허가 고지를 포함해야 한다. 이 문서는 해당 attribution을 보존한다.
|
|
10
|
+
|
|
11
|
+
이번 통합은 upstream 파일을 그대로 복제하지 않고, 공개 포털 접근 경로·정규화 스키마·fail-closed 원칙을 참고해 k-skill-proxy의 Node.js 구현과 k-skill CLI 계약으로 재작성했다.
|
|
12
|
+
|
|
13
|
+
## 공식 데이터 출처
|
|
14
|
+
|
|
15
|
+
| 소스 | 공식 URL | k-skill 처리 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| K-Startup | https://www.data.go.kr/data/15125364/openapi.do | 공공데이터포털 Open API 응답의 공고 메타데이터와 링크 |
|
|
18
|
+
| 기업마당 | https://www.bizinfo.go.kr/ | 공개 모집 목록의 메타데이터와 공식 상세 링크 |
|
|
19
|
+
| NIPA | https://www.nipa.kr/home/2-2 | 공개 사업공고 목록의 메타데이터와 공식 상세 링크 |
|
|
20
|
+
| KOCCA | https://www.kocca.kr/kocca/pims/list.do | 공개 지원공고 목록의 메타데이터와 공식 상세 링크 |
|
|
21
|
+
| SMTECH | https://www.smtech.go.kr/front/ifg/no/notice02_list.do | 공개 R&D 공고 목록의 메타데이터와 공식 상세 링크 |
|
|
22
|
+
|
|
23
|
+
## 재배포 판단
|
|
24
|
+
|
|
25
|
+
- **오픈소스 코드**: MIT 조건에 따라 attribution을 유지하면 재사용·수정·재배포 가능.
|
|
26
|
+
- **K-Startup Open API 데이터**: 데이터셋 상세 페이지의 이용조건을 따라 사용하며 출처를 표시한다.
|
|
27
|
+
- **각 기관의 공고 원문·첨부파일**: 개별 저작권·공공누리·제3자 권리가 다를 수 있으므로 일괄 재배포 가능하다고 간주하지 않는다.
|
|
28
|
+
- **k-skill-proxy 응답**: 제목, 기관, 날짜, 식별자, 공식 링크 같은 사실 메타데이터만 구조화한다. 공고 본문·첨부파일을 저장하거나 미러링하지 않는다.
|
|
29
|
+
|
|
30
|
+
공고 내용을 인용하거나 첨부파일을 재배포해야 하는 별도 기능은 각 자료에 표시된 공공누리 유형과 이용약관을 건별 확인한 뒤 구현해야 한다.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Government support survey helper. Proxy-first, stdlib only."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.parse
|
|
11
|
+
import urllib.request
|
|
12
|
+
|
|
13
|
+
DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
|
|
14
|
+
VALID_SOURCES = ("kstartup", "bizinfo", "nipa", "kocca", "smtech")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_url(args: argparse.Namespace) -> str:
|
|
18
|
+
base = (args.proxy_base_url or os.environ.get("KSKILL_PROXY_BASE_URL")
|
|
19
|
+
or DEFAULT_PROXY_BASE_URL).rstrip("/")
|
|
20
|
+
params = {
|
|
21
|
+
"sources": ",".join(args.sources),
|
|
22
|
+
"maxPages": str(args.max_pages),
|
|
23
|
+
"perPage": str(args.per_page),
|
|
24
|
+
}
|
|
25
|
+
if args.keyword:
|
|
26
|
+
params["keyword"] = args.keyword
|
|
27
|
+
return f"{base}/v1/government-support/survey?{urllib.parse.urlencode(params)}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def parser() -> argparse.ArgumentParser:
|
|
31
|
+
result = argparse.ArgumentParser(
|
|
32
|
+
description="K-Startup·기업마당·NIPA·KOCCA·SMTECH 정부지원 공고 전수조사"
|
|
33
|
+
)
|
|
34
|
+
result.add_argument("--sources", nargs="+", choices=VALID_SOURCES, default=list(VALID_SOURCES))
|
|
35
|
+
result.add_argument("--keyword", default="")
|
|
36
|
+
result.add_argument("--max-pages", type=int, default=1)
|
|
37
|
+
result.add_argument("--per-page", type=int, default=100)
|
|
38
|
+
result.add_argument("--proxy-base-url")
|
|
39
|
+
result.add_argument("--timeout", type=float, default=30)
|
|
40
|
+
result.add_argument("--compact", action="store_true")
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main(argv: list[str] | None = None) -> int:
|
|
45
|
+
args = parser().parse_args(argv)
|
|
46
|
+
if not 1 <= args.max_pages <= 10:
|
|
47
|
+
parser().error("--max-pages must be between 1 and 10")
|
|
48
|
+
if not 1 <= args.per_page <= 100:
|
|
49
|
+
parser().error("--per-page must be between 1 and 100")
|
|
50
|
+
request = urllib.request.Request(
|
|
51
|
+
build_url(args),
|
|
52
|
+
headers={"Accept": "application/json", "User-Agent": "k-skill-government-support-survey/1.0"},
|
|
53
|
+
)
|
|
54
|
+
try:
|
|
55
|
+
with urllib.request.urlopen(request, timeout=args.timeout) as response:
|
|
56
|
+
payload = json.load(response)
|
|
57
|
+
except urllib.error.HTTPError as error:
|
|
58
|
+
body = error.read().decode("utf-8", errors="replace")
|
|
59
|
+
print(body or f"HTTP {error.code}", file=sys.stderr)
|
|
60
|
+
return 2
|
|
61
|
+
except (urllib.error.URLError, TimeoutError) as error:
|
|
62
|
+
print(f"k-skill-proxy request failed: {error}", file=sys.stderr)
|
|
63
|
+
return 2
|
|
64
|
+
|
|
65
|
+
json.dump(payload, sys.stdout, ensure_ascii=False, indent=None if args.compact else 2)
|
|
66
|
+
sys.stdout.write("\n")
|
|
67
|
+
return 0 if payload.get("complete") else 2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "government-support-survey",
|
|
3
|
+
"description": "K-Startup·기업마당·NIPA·KOCCA·SMTECH의 공개 정부지원 공고를 k-skill-proxy로 전수조사하고 프로젝트 적합성을 보수적으로 분류한다. 사용자가 정부지원사업, 창업지원, 사업화·R&D·바우처·입주공간 공고를 한 번에 찾아달라고 할 때 사용한다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"proxy",
|
|
6
|
+
"browser",
|
|
7
|
+
"action:submission"
|
|
8
|
+
],
|
|
9
|
+
"frontmatter": "name: government-support-survey\ndescription: K-Startup·기업마당·NIPA·KOCCA·SMTECH의 공개 정부지원 공고를 k-skill-proxy로 전수조사하고 프로젝트 적합성을 보수적으로 분류한다. 사용자가 정부지원사업, 창업지원, 사업화·R&D·바우처·입주공간 공고를 한 번에 찾아달라고 할 때 사용한다."
|
|
10
|
+
}
|