daiso 1.0.5 → 1.0.7
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/README.md +101 -7
- package/dist/api/actionsProxy.js +111 -21
- package/dist/api/configStatus.js +23 -0
- package/dist/api/gs25Handlers.js +68 -22
- package/dist/api/handlers.js +32 -3
- package/dist/api/healthChecks.js +233 -20
- package/dist/api/lottemartHandlers.js +17 -1
- package/dist/api/placesHandlers.js +44 -0
- package/dist/api/response.js +2 -0
- package/dist/api/routes/healthRoutes.js +20 -3
- package/dist/api/routes/placesRoutes.js +9 -0
- package/dist/bin.js +0 -0
- package/dist/cli/args.js +21 -0
- package/dist/cli/commandOptions.js +96 -0
- package/dist/cli/commands/convenience.js +73 -1
- package/dist/cli/commands/daiso.js +18 -2
- package/dist/cli/http.js +58 -0
- package/dist/cli/interactiveItemSearch.js +4 -2
- package/dist/cli/interactivePrompt.js +3 -2
- package/dist/cli.js +25 -1
- package/dist/cliHelp.js +20 -6
- package/dist/cliInteractive.js +75 -6
- package/dist/cliPicker.js +1 -0
- package/dist/cliRenderer.js +93 -3
- package/dist/core/errors.js +66 -0
- package/dist/core/registry.js +246 -3
- package/dist/index.js +19 -4
- package/dist/pages/openapiFullSpec.js +3 -0
- package/dist/pages/openapiSpecActions.js +308 -42
- package/dist/pages/openapiSpecPathsCu.js +7 -0
- package/dist/pages/openapiSpecPathsLotteMart.js +14 -0
- package/dist/pages/openapiSpecPathsOliveyoung.js +35 -0
- package/dist/pages/openapiSpecPathsPlaces.js +85 -0
- package/dist/pages/promptReferenceText.js +16 -7
- package/dist/pages/promptRetailCinemaText.js +24 -2
- package/dist/services/daiso/client.js +8 -0
- package/dist/services/daiso/index.js +2 -0
- package/dist/services/daiso/tools/checkInventory.js +4 -2
- package/dist/services/daiso/tools/findInventoryByName.js +222 -0
- package/dist/services/daiso/tools/getDisplayLocation.js +2 -2
- package/dist/services/emart24/client.js +9 -0
- package/dist/services/emart24/tools/searchProducts.js +27 -17
- package/dist/services/gs25/api.js +3 -0
- package/dist/services/gs25/client.js +216 -30
- package/dist/services/gs25/index.js +11 -3
- package/dist/services/gs25/tools/checkInventory.js +39 -12
- package/dist/services/gs25/tools/findNearbyStores.js +19 -5
- package/dist/services/gs25/tools/searchProducts.js +39 -25
- package/dist/services/lottemart/client.js +10 -1
- package/dist/services/lottemart/tools/searchProducts.js +8 -1
- package/dist/services/lottemart/zetta.js +1 -1
- package/dist/services/oliveyoung/client.js +206 -86
- package/dist/services/oliveyoung/tools/checkInventory.js +7 -2
- package/dist/services/places/client.js +116 -0
- package/dist/services/places/index.js +23 -0
- package/dist/services/places/tools/searchNearby.js +56 -0
- package/dist/services/places/types.js +1 -0
- package/dist/services/seveneleven/client.js +7 -0
- package/dist/services/seveneleven/tools/searchProducts.js +29 -18
- package/dist/utils/http.js +82 -10
- package/dist/utils/zyte.js +74 -26
- package/openapi.json +22 -3
- package/openapi.yaml +16 -3
- package/package.json +28 -9
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 명령별 허용 옵션 정의
|
|
3
|
+
*/
|
|
4
|
+
import { writeUnknownOptionError } from './args.js';
|
|
5
|
+
export const COMMAND_OPTION_ALLOWLISTS = {
|
|
6
|
+
version: [],
|
|
7
|
+
url: [],
|
|
8
|
+
health: [],
|
|
9
|
+
claude: ['exec'],
|
|
10
|
+
products: ['help', 'json', 'page', 'pageSize'],
|
|
11
|
+
product: ['help', 'json'],
|
|
12
|
+
stores: ['help', 'json', 'keyword', 'sido', 'gugun', 'dong', 'limit'],
|
|
13
|
+
inventory: ['help', 'json', 'keyword', 'lat', 'lng', 'page', 'pageSize'],
|
|
14
|
+
'display-location': ['help', 'json'],
|
|
15
|
+
places: ['help', 'json', 'location', 'category', 'keyword', 'limit', 'start', 'sort'],
|
|
16
|
+
'cu-stores': ['help', 'json', 'keyword', 'lat', 'lng', 'limit'],
|
|
17
|
+
'cu-inventory': [
|
|
18
|
+
'help',
|
|
19
|
+
'json',
|
|
20
|
+
'storeKeyword',
|
|
21
|
+
'lat',
|
|
22
|
+
'lng',
|
|
23
|
+
'size',
|
|
24
|
+
'offset',
|
|
25
|
+
'searchSort',
|
|
26
|
+
'storeLimit',
|
|
27
|
+
],
|
|
28
|
+
'lottecinema-theaters': ['help', 'json', 'keyword', 'lat', 'lng', 'playDate', 'limit'],
|
|
29
|
+
'lottecinema-movies': [
|
|
30
|
+
'help',
|
|
31
|
+
'json',
|
|
32
|
+
'keyword',
|
|
33
|
+
'playDate',
|
|
34
|
+
'theaterId',
|
|
35
|
+
'movieId',
|
|
36
|
+
'lat',
|
|
37
|
+
'lng',
|
|
38
|
+
],
|
|
39
|
+
'lottecinema-seats': [
|
|
40
|
+
'help',
|
|
41
|
+
'json',
|
|
42
|
+
'keyword',
|
|
43
|
+
'playDate',
|
|
44
|
+
'theaterId',
|
|
45
|
+
'movieId',
|
|
46
|
+
'lat',
|
|
47
|
+
'lng',
|
|
48
|
+
'limit',
|
|
49
|
+
],
|
|
50
|
+
'emart24-stores': [
|
|
51
|
+
'help',
|
|
52
|
+
'json',
|
|
53
|
+
'keyword',
|
|
54
|
+
'area1',
|
|
55
|
+
'area2',
|
|
56
|
+
'lat',
|
|
57
|
+
'lng',
|
|
58
|
+
'service24h',
|
|
59
|
+
'limit',
|
|
60
|
+
],
|
|
61
|
+
'emart24-products': ['help', 'json', 'page', 'pageSize'],
|
|
62
|
+
'emart24-inventory': [
|
|
63
|
+
'help',
|
|
64
|
+
'json',
|
|
65
|
+
'bizNoArr',
|
|
66
|
+
'storeKeyword',
|
|
67
|
+
'area1',
|
|
68
|
+
'area2',
|
|
69
|
+
'lat',
|
|
70
|
+
'lng',
|
|
71
|
+
'storeLimit',
|
|
72
|
+
],
|
|
73
|
+
'lottemart-stores': ['help', 'json', 'keyword', 'area', 'brandVariant', 'lat', 'lng', 'limit'],
|
|
74
|
+
'lottemart-products': [
|
|
75
|
+
'help',
|
|
76
|
+
'json',
|
|
77
|
+
'storeCode',
|
|
78
|
+
'storeName',
|
|
79
|
+
'area',
|
|
80
|
+
'brandVariant',
|
|
81
|
+
'pageLimit',
|
|
82
|
+
],
|
|
83
|
+
'gs25-stores': ['help', 'json', 'keyword', 'lat', 'lng', 'serviceCode', 'limit'],
|
|
84
|
+
'gs25-products': ['help', 'json', 'serviceCode', 'limit'],
|
|
85
|
+
'gs25-inventory': ['help', 'json', 'storeKeyword', 'lat', 'lng', 'serviceCode', 'storeLimit'],
|
|
86
|
+
'seveneleven-products': ['help', 'json', 'page', 'size', 'sort'],
|
|
87
|
+
'seveneleven-stores': ['help', 'json', 'limit'],
|
|
88
|
+
'seveneleven-popwords': ['help', 'json', 'label'],
|
|
89
|
+
'seveneleven-catalog': ['help', 'json', 'includeIssues', 'includeExhibition', 'limit'],
|
|
90
|
+
};
|
|
91
|
+
const COMMAND_OPTION_HINTS = {
|
|
92
|
+
inventory: '매장명은 --keyword로 전달하세요. 예: daiso inventory 1034604 --keyword 강남역',
|
|
93
|
+
};
|
|
94
|
+
export function validateCommandOptions(command, options, writeErr) {
|
|
95
|
+
return writeUnknownOptionError(options, COMMAND_OPTION_ALLOWLISTS[command], writeErr, COMMAND_OPTION_HINTS[command]);
|
|
96
|
+
}
|
|
@@ -4,7 +4,28 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { printCommandHelp } from '../../cliHelp.js';
|
|
6
6
|
import { parseCliArgs, toUrl, applyOptionsToQuery, toQueryOptions } from '../args.js';
|
|
7
|
+
import { validateCommandOptions } from '../commandOptions.js';
|
|
7
8
|
import { requestAndPrintResponse } from '../http.js';
|
|
9
|
+
export async function handlePlaces(options, deps) {
|
|
10
|
+
const parsed = parseCliArgs(options);
|
|
11
|
+
if (parsed.options.help === 'true') {
|
|
12
|
+
return printCommandHelp('places', deps.writeOut, deps.writeErr);
|
|
13
|
+
}
|
|
14
|
+
const location = parsed.positionals[0];
|
|
15
|
+
if (location) {
|
|
16
|
+
parsed.options = { location, ...parsed.options };
|
|
17
|
+
}
|
|
18
|
+
if (validateCommandOptions('places', parsed.options, deps.writeErr)) {
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
if (!parsed.options.location && !parsed.options.keyword) {
|
|
22
|
+
deps.writeErr('places 명령은 위치나 검색어가 필요합니다. 예: daiso places 강남역 --category cafe');
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
const targetUrl = toUrl('/api/places/search');
|
|
26
|
+
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
27
|
+
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'places', parsed.options.json === 'true');
|
|
28
|
+
}
|
|
8
29
|
export async function handleCuStores(options, deps) {
|
|
9
30
|
const parsed = parseCliArgs(options);
|
|
10
31
|
if (parsed.options.help === 'true') {
|
|
@@ -14,6 +35,9 @@ export async function handleCuStores(options, deps) {
|
|
|
14
35
|
if (keyword) {
|
|
15
36
|
parsed.options.keyword = keyword;
|
|
16
37
|
}
|
|
38
|
+
if (validateCommandOptions('cu-stores', parsed.options, deps.writeErr)) {
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
17
41
|
const targetUrl = toUrl('/api/cu/stores');
|
|
18
42
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
19
43
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'cu-stores', parsed.options.json === 'true');
|
|
@@ -23,6 +47,9 @@ export async function handleCuInventory(options, deps) {
|
|
|
23
47
|
if (parsed.options.help === 'true') {
|
|
24
48
|
return printCommandHelp('cu-inventory', deps.writeOut, deps.writeErr);
|
|
25
49
|
}
|
|
50
|
+
if (validateCommandOptions('cu-inventory', parsed.options, deps.writeErr)) {
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
26
53
|
const keyword = parsed.positionals[0];
|
|
27
54
|
if (!keyword) {
|
|
28
55
|
deps.writeErr('cu-inventory 명령은 검색어가 필요합니다. 예: daiso cu-inventory 과자');
|
|
@@ -42,6 +69,9 @@ export async function handleEmart24Stores(options, deps) {
|
|
|
42
69
|
if (keyword) {
|
|
43
70
|
parsed.options.keyword = keyword;
|
|
44
71
|
}
|
|
72
|
+
if (validateCommandOptions('emart24-stores', parsed.options, deps.writeErr)) {
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
45
75
|
const targetUrl = toUrl('/api/emart24/stores');
|
|
46
76
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
47
77
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'emart24-stores', parsed.options.json === 'true');
|
|
@@ -51,6 +81,9 @@ export async function handleEmart24Products(options, deps) {
|
|
|
51
81
|
if (parsed.options.help === 'true') {
|
|
52
82
|
return printCommandHelp('emart24-products', deps.writeOut, deps.writeErr);
|
|
53
83
|
}
|
|
84
|
+
if (validateCommandOptions('emart24-products', parsed.options, deps.writeErr)) {
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
54
87
|
const keyword = parsed.positionals[0];
|
|
55
88
|
if (!keyword) {
|
|
56
89
|
deps.writeErr('emart24-products 명령은 검색어가 필요합니다. 예: daiso emart24-products 두바이');
|
|
@@ -66,6 +99,9 @@ export async function handleEmart24Inventory(options, deps) {
|
|
|
66
99
|
if (parsed.options.help === 'true') {
|
|
67
100
|
return printCommandHelp('emart24-inventory', deps.writeOut, deps.writeErr);
|
|
68
101
|
}
|
|
102
|
+
if (validateCommandOptions('emart24-inventory', parsed.options, deps.writeErr)) {
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
69
105
|
const pluCd = parsed.positionals[0];
|
|
70
106
|
const bizNoArr = parsed.options.bizNoArr;
|
|
71
107
|
if (!pluCd || !bizNoArr) {
|
|
@@ -86,6 +122,9 @@ export async function handleLotteMartStores(options, deps) {
|
|
|
86
122
|
if (keyword) {
|
|
87
123
|
parsed.options.keyword = keyword;
|
|
88
124
|
}
|
|
125
|
+
if (validateCommandOptions('lottemart-stores', parsed.options, deps.writeErr)) {
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
89
128
|
const targetUrl = toUrl('/api/lottemart/stores');
|
|
90
129
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
91
130
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottemart-stores', parsed.options.json === 'true');
|
|
@@ -95,13 +134,16 @@ export async function handleLotteMartProducts(options, deps) {
|
|
|
95
134
|
if (parsed.options.help === 'true') {
|
|
96
135
|
return printCommandHelp('lottemart-products', deps.writeOut, deps.writeErr);
|
|
97
136
|
}
|
|
137
|
+
if (validateCommandOptions('lottemart-products', parsed.options, deps.writeErr)) {
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
98
140
|
const keyword = parsed.positionals[0];
|
|
99
141
|
if (!keyword) {
|
|
100
142
|
deps.writeErr('lottemart-products 명령은 검색어가 필요합니다. 예: daiso lottemart-products 콜라 --storeName 강변점');
|
|
101
143
|
return 1;
|
|
102
144
|
}
|
|
103
145
|
if (!parsed.options.storeCode && !parsed.options.storeName) {
|
|
104
|
-
deps.writeErr('lottemart-products 명령은 --storeCode 또는 --storeName이 필요합니다. 예: daiso lottemart-products 콜라 --storeName 강변점');
|
|
146
|
+
deps.writeErr('lottemart-products 명령은 --storeCode 또는 --storeName이 필요합니다. 매장을 모르면 먼저 daiso lottemart-stores 잠실 --area 서울 명령으로 storeName/storeCode를 확인하세요. 예: daiso lottemart-products 콜라 --storeName 강변점');
|
|
105
147
|
return 1;
|
|
106
148
|
}
|
|
107
149
|
const targetUrl = toUrl('/api/lottemart/products');
|
|
@@ -118,6 +160,9 @@ export async function handleGs25Stores(options, deps) {
|
|
|
118
160
|
if (keyword) {
|
|
119
161
|
parsed.options.keyword = keyword;
|
|
120
162
|
}
|
|
163
|
+
if (validateCommandOptions('gs25-stores', parsed.options, deps.writeErr)) {
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
121
166
|
const targetUrl = toUrl('/api/gs25/stores');
|
|
122
167
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
123
168
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'gs25-stores', parsed.options.json === 'true');
|
|
@@ -127,6 +172,9 @@ export async function handleGs25Products(options, deps) {
|
|
|
127
172
|
if (parsed.options.help === 'true') {
|
|
128
173
|
return printCommandHelp('gs25-products', deps.writeOut, deps.writeErr);
|
|
129
174
|
}
|
|
175
|
+
if (validateCommandOptions('gs25-products', parsed.options, deps.writeErr)) {
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
130
178
|
const keyword = parsed.positionals[0];
|
|
131
179
|
if (!keyword) {
|
|
132
180
|
deps.writeErr('gs25-products 명령은 검색어가 필요합니다. 예: daiso gs25-products 오감자');
|
|
@@ -142,6 +190,9 @@ export async function handleGs25Inventory(options, deps) {
|
|
|
142
190
|
if (parsed.options.help === 'true') {
|
|
143
191
|
return printCommandHelp('gs25-inventory', deps.writeOut, deps.writeErr);
|
|
144
192
|
}
|
|
193
|
+
if (validateCommandOptions('gs25-inventory', parsed.options, deps.writeErr)) {
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
145
196
|
const keyword = parsed.positionals[0];
|
|
146
197
|
if (!keyword) {
|
|
147
198
|
deps.writeErr('gs25-inventory 명령은 검색어가 필요합니다. 예: daiso gs25-inventory 오감자');
|
|
@@ -157,6 +208,9 @@ export async function handleSevenElevenProducts(options, deps) {
|
|
|
157
208
|
if (parsed.options.help === 'true') {
|
|
158
209
|
return printCommandHelp('seveneleven-products', deps.writeOut, deps.writeErr);
|
|
159
210
|
}
|
|
211
|
+
if (validateCommandOptions('seveneleven-products', parsed.options, deps.writeErr)) {
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
160
214
|
const query = parsed.positionals[0];
|
|
161
215
|
if (!query) {
|
|
162
216
|
deps.writeErr('seveneleven-products 명령은 검색어가 필요합니다. 예: daiso seveneleven-products 삼각김밥');
|
|
@@ -172,6 +226,9 @@ export async function handleSevenElevenStores(options, deps) {
|
|
|
172
226
|
if (parsed.options.help === 'true') {
|
|
173
227
|
return printCommandHelp('seveneleven-stores', deps.writeOut, deps.writeErr);
|
|
174
228
|
}
|
|
229
|
+
if (validateCommandOptions('seveneleven-stores', parsed.options, deps.writeErr)) {
|
|
230
|
+
return 1;
|
|
231
|
+
}
|
|
175
232
|
const keyword = parsed.positionals[0];
|
|
176
233
|
if (!keyword) {
|
|
177
234
|
deps.writeErr('seveneleven-stores 명령은 검색어가 필요합니다. 예: daiso seveneleven-stores 안산 중앙역');
|
|
@@ -187,6 +244,9 @@ export async function handleSevenElevenPopwords(options, deps) {
|
|
|
187
244
|
if (parsed.options.help === 'true') {
|
|
188
245
|
return printCommandHelp('seveneleven-popwords', deps.writeOut, deps.writeErr);
|
|
189
246
|
}
|
|
247
|
+
if (validateCommandOptions('seveneleven-popwords', parsed.options, deps.writeErr)) {
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
190
250
|
const targetUrl = toUrl('/api/seveneleven/popwords');
|
|
191
251
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
192
252
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-popwords', parsed.options.json === 'true');
|
|
@@ -196,6 +256,9 @@ export async function handleSevenElevenCatalog(options, deps) {
|
|
|
196
256
|
if (parsed.options.help === 'true') {
|
|
197
257
|
return printCommandHelp('seveneleven-catalog', deps.writeOut, deps.writeErr);
|
|
198
258
|
}
|
|
259
|
+
if (validateCommandOptions('seveneleven-catalog', parsed.options, deps.writeErr)) {
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
199
262
|
const targetUrl = toUrl('/api/seveneleven/catalog');
|
|
200
263
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
201
264
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-catalog', parsed.options.json === 'true');
|
|
@@ -209,6 +272,9 @@ export async function handleLottecinemaTheaters(options, deps) {
|
|
|
209
272
|
if (keyword) {
|
|
210
273
|
parsed.options.keyword = keyword;
|
|
211
274
|
}
|
|
275
|
+
if (validateCommandOptions('lottecinema-theaters', parsed.options, deps.writeErr)) {
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
212
278
|
const targetUrl = toUrl('/api/lottecinema/theaters');
|
|
213
279
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
214
280
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-theaters', parsed.options.json === 'true');
|
|
@@ -222,6 +288,9 @@ export async function handleLottecinemaMovies(options, deps) {
|
|
|
222
288
|
if (keyword) {
|
|
223
289
|
parsed.options.keyword = keyword;
|
|
224
290
|
}
|
|
291
|
+
if (validateCommandOptions('lottecinema-movies', parsed.options, deps.writeErr)) {
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
225
294
|
const targetUrl = toUrl('/api/lottecinema/movies');
|
|
226
295
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
227
296
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-movies', parsed.options.json === 'true');
|
|
@@ -235,6 +304,9 @@ export async function handleLottecinemaSeats(options, deps) {
|
|
|
235
304
|
if (keyword) {
|
|
236
305
|
parsed.options.keyword = keyword;
|
|
237
306
|
}
|
|
307
|
+
if (validateCommandOptions('lottecinema-seats', parsed.options, deps.writeErr)) {
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
238
310
|
const targetUrl = toUrl('/api/lottecinema/seats');
|
|
239
311
|
applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
|
|
240
312
|
return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-seats', parsed.options.json === 'true');
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { printCommandHelp } from '../../cliHelp.js';
|
|
5
5
|
import { parseCliArgs, toUrl, applyOptionsToQuery, toQueryOptions } from '../args.js';
|
|
6
|
+
import { validateCommandOptions } from '../commandOptions.js';
|
|
6
7
|
import { requestAndPrintResponse, requestAndPrintStoresWithKeywordFallback } from '../http.js';
|
|
7
8
|
export async function handleGet(options, deps) {
|
|
8
9
|
const parsed = parseCliArgs(options);
|
|
@@ -23,6 +24,9 @@ export async function handleProducts(options, deps) {
|
|
|
23
24
|
if (parsed.options.help === 'true') {
|
|
24
25
|
return printCommandHelp('products', deps.writeOut, deps.writeErr);
|
|
25
26
|
}
|
|
27
|
+
if (validateCommandOptions('products', parsed.options, deps.writeErr)) {
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
26
30
|
const query = parsed.positionals[0];
|
|
27
31
|
if (!query) {
|
|
28
32
|
deps.writeErr('products 명령은 검색어가 필요합니다. 예: daiso products 수납박스');
|
|
@@ -38,6 +42,9 @@ export async function handleProduct(options, deps) {
|
|
|
38
42
|
if (parsed.options.help === 'true') {
|
|
39
43
|
return printCommandHelp('product', deps.writeOut, deps.writeErr);
|
|
40
44
|
}
|
|
45
|
+
if (validateCommandOptions('product', parsed.options, deps.writeErr)) {
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
41
48
|
const productId = parsed.positionals[0];
|
|
42
49
|
if (!productId) {
|
|
43
50
|
deps.writeErr('product 명령은 제품 ID가 필요합니다. 예: daiso product 1034604');
|
|
@@ -56,6 +63,9 @@ export async function handleStores(options, deps) {
|
|
|
56
63
|
if (keyword) {
|
|
57
64
|
parsed.options.keyword = keyword;
|
|
58
65
|
}
|
|
66
|
+
if (validateCommandOptions('stores', parsed.options, deps.writeErr)) {
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
59
69
|
if (!parsed.options.keyword && !parsed.options.sido) {
|
|
60
70
|
deps.writeErr('stores 명령은 keyword 또는 --sido가 필요합니다. 예: daiso stores 강남역 / daiso stores --sido 서울');
|
|
61
71
|
return 1;
|
|
@@ -67,9 +77,12 @@ export async function handleInventory(options, deps) {
|
|
|
67
77
|
if (parsed.options.help === 'true') {
|
|
68
78
|
return printCommandHelp('inventory', deps.writeOut, deps.writeErr);
|
|
69
79
|
}
|
|
80
|
+
if (validateCommandOptions('inventory', parsed.options, deps.writeErr)) {
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
70
83
|
const productId = parsed.positionals[0];
|
|
71
84
|
if (!productId) {
|
|
72
|
-
deps.writeErr('inventory 명령은 제품 ID가 필요합니다. 예: daiso inventory 1034604 --keyword 강남역');
|
|
85
|
+
deps.writeErr('inventory 명령은 제품 ID가 필요합니다. 제품명만 알면 먼저 daiso products 수납박스 명령으로 productId를 확인하세요. 예: daiso inventory 1034604 --keyword 강남역');
|
|
73
86
|
return 1;
|
|
74
87
|
}
|
|
75
88
|
const targetUrl = toUrl('/api/daiso/inventory');
|
|
@@ -82,10 +95,13 @@ export async function handleDisplayLocation(options, deps) {
|
|
|
82
95
|
if (parsed.options.help === 'true') {
|
|
83
96
|
return printCommandHelp('display-location', deps.writeOut, deps.writeErr);
|
|
84
97
|
}
|
|
98
|
+
if (validateCommandOptions('display-location', parsed.options, deps.writeErr)) {
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
85
101
|
const productId = parsed.positionals[0];
|
|
86
102
|
const storeCode = parsed.positionals[1];
|
|
87
103
|
if (!productId || !storeCode) {
|
|
88
|
-
deps.writeErr('display-location 명령은 productId와 storeCode가 필요합니다. 예: daiso display-location 1034604 04515');
|
|
104
|
+
deps.writeErr('display-location 명령은 productId와 storeCode가 필요합니다. storeCode를 모르면 먼저 daiso inventory 1034604 --keyword 매장명 결과의 storeInventory.stores[].storeCode를 확인하세요. 예: daiso display-location 1034604 04515');
|
|
89
105
|
return 1;
|
|
90
106
|
}
|
|
91
107
|
const targetUrl = toUrl('/api/daiso/display-location');
|
package/dist/cli/http.js
CHANGED
|
@@ -4,6 +4,61 @@
|
|
|
4
4
|
import { renderApiEnvelope } from '../cliRenderer.js';
|
|
5
5
|
import { buildDaisoStoreKeywordVariants } from '../utils/daisoKeyword.js';
|
|
6
6
|
import { applyOptionsToQuery, toUrl } from './args.js';
|
|
7
|
+
function parseErrorCode(bodyText) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(bodyText);
|
|
10
|
+
return typeof parsed.error?.code === 'string' ? parsed.error.code : '';
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function buildCliHint(command, url, bodyText) {
|
|
17
|
+
if (command !== 'get') {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const code = parseErrorCode(bodyText);
|
|
21
|
+
if (url.pathname === '/api/daiso/inventory' || code === 'MISSING_PRODUCT_ID') {
|
|
22
|
+
return [
|
|
23
|
+
'힌트: 제품명만 알면 먼저 daiso products <상품명> 명령으로 productId를 확인하세요.',
|
|
24
|
+
'다음 명령 예시: daiso inventory <productId> --keyword 강남역',
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
if (url.pathname === '/api/daiso/products') {
|
|
28
|
+
return ['힌트: 제품 검색은 daiso products <상품명> 명령을 사용할 수 있습니다.'];
|
|
29
|
+
}
|
|
30
|
+
if (url.pathname === '/api/daiso/display-location' || code === 'MISSING_STORE_CODE') {
|
|
31
|
+
return [
|
|
32
|
+
'힌트: storeCode는 daiso inventory <productId> --keyword <매장명> 결과에서 확인하세요.',
|
|
33
|
+
'다음 명령 예시: daiso display-location <productId> <storeCode>',
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
if (url.pathname === '/api/lottemart/products') {
|
|
37
|
+
return [
|
|
38
|
+
'힌트: 롯데마트 상품 조회는 매장 정보가 필요합니다.',
|
|
39
|
+
'다음 명령 예시: daiso lottemart-products <상품명> --storeName <매장명>',
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
if (url.pathname === '/api/gs25/inventory') {
|
|
43
|
+
return [
|
|
44
|
+
'힌트: GS25 상품명만 알면 daiso gs25-inventory <상품명> --storeKeyword <매장명> 명령을 사용하세요.',
|
|
45
|
+
'itemCode만 알면 daiso gs25-inventory <itemCode> 형식 대신 daiso get /api/gs25/inventory --itemCode <itemCode> --storeKeyword <매장명> 을 사용하세요.',
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
if (url.pathname === '/api/emart24/inventory') {
|
|
49
|
+
return [
|
|
50
|
+
'힌트: 이마트24 상품명만 알면 daiso emart24-products <상품명> 명령으로 pluCd를 확인하세요.',
|
|
51
|
+
'다음 명령 예시: daiso emart24-inventory <pluCd> --storeKeyword 강남',
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
if (url.pathname === '/api/seveneleven/inventory') {
|
|
55
|
+
return [
|
|
56
|
+
'힌트: 세븐일레븐 재고는 매장 키워드가 필요합니다.',
|
|
57
|
+
'다음 명령 예시: daiso get /api/seveneleven/inventory --keyword <상품명> --storeKeyword 강남',
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
7
62
|
export async function requestAndPrintResponse(fetchImpl, writeOut, writeErr, url, command, asJson) {
|
|
8
63
|
try {
|
|
9
64
|
const response = await fetchImpl(url.toString());
|
|
@@ -12,6 +67,9 @@ export async function requestAndPrintResponse(fetchImpl, writeOut, writeErr, url
|
|
|
12
67
|
writeErr(`요청 실패: HTTP ${response.status}`);
|
|
13
68
|
if (bodyText) {
|
|
14
69
|
writeErr(bodyText);
|
|
70
|
+
for (const hint of buildCliHint(command, url, bodyText)) {
|
|
71
|
+
writeErr(hint);
|
|
72
|
+
}
|
|
15
73
|
}
|
|
16
74
|
return 1;
|
|
17
75
|
}
|
|
@@ -93,8 +93,10 @@ export async function runLotteCinemaSearch(deps, prompt, theater) {
|
|
|
93
93
|
deps.writeOut(` ...외 ${seats.length - 15}건`);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
export async function runDaisoItemSearch(deps, prompt, store) {
|
|
97
|
-
const keyword =
|
|
96
|
+
export async function runDaisoItemSearch(deps, prompt, store, initialKeyword) {
|
|
97
|
+
const keyword = initialKeyword && initialKeyword.trim().length > 0
|
|
98
|
+
? initialKeyword.trim()
|
|
99
|
+
: await askNonEmpty(prompt, '찾을 상품 키워드를 입력하세요: ');
|
|
98
100
|
const productsPayload = await fetchEnvelope(deps.fetchImpl, '/api/daiso/products', {
|
|
99
101
|
q: keyword,
|
|
100
102
|
pageSize: '10',
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 인터랙티브 CLI 입력/출력 헬퍼
|
|
3
3
|
*/
|
|
4
|
-
export async function askMenu(prompt, title, options) {
|
|
4
|
+
export async function askMenu(prompt, title, options, writeOut) {
|
|
5
5
|
while (true) {
|
|
6
6
|
const answer = await prompt.ask(`${title} `);
|
|
7
7
|
const picked = Number.parseInt(answer, 10);
|
|
8
8
|
if (Number.isNaN(picked) || picked < 0 || picked > options.length) {
|
|
9
|
+
writeOut?.(`번호로 입력하세요. 0부터 ${options.length} 사이에서 선택할 수 있습니다.`);
|
|
9
10
|
continue;
|
|
10
11
|
}
|
|
11
12
|
return picked;
|
|
@@ -36,7 +37,7 @@ export async function askNextAction(prompt, writeOut) {
|
|
|
36
37
|
writeOut('1. 같은 매장에서 다른 상품 찾기');
|
|
37
38
|
writeOut('2. 다른 매장/서비스 다시 선택하기');
|
|
38
39
|
writeOut('3. 종료하기');
|
|
39
|
-
const choice = await askMenu(prompt, '번호를 선택하세요:', ['same-store', 'change-store', 'exit']);
|
|
40
|
+
const choice = await askMenu(prompt, '번호를 선택하세요:', ['same-store', 'change-store', 'exit'], writeOut);
|
|
40
41
|
return choice === 1 ? 'same-store' : choice === 2 ? 'change-store' : 'exit';
|
|
41
42
|
}
|
|
42
43
|
export function printStoreDetail(writeOut, store) {
|
package/dist/cli.js
CHANGED
|
@@ -9,8 +9,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { printHelp, printCommandHelp } from './cliHelp.js';
|
|
10
10
|
import { createDefaultDeps } from './cli/deps.js';
|
|
11
11
|
import { DEFAULT_BASE_URL, DEFAULT_MCP_URL } from './cli/constants.js';
|
|
12
|
+
import { parseCliArgs } from './cli/args.js';
|
|
13
|
+
import { validateCommandOptions } from './cli/commandOptions.js';
|
|
12
14
|
import { handleGet, handleProducts, handleProduct, handleStores, handleInventory, handleDisplayLocation, } from './cli/commands/daiso.js';
|
|
13
|
-
import { handleCuStores, handleCuInventory, handleEmart24Stores, handleEmart24Products, handleEmart24Inventory, handleLotteMartStores, handleLotteMartProducts, handleGs25Stores, handleGs25Products, handleGs25Inventory, handleSevenElevenProducts, handleSevenElevenStores, handleSevenElevenPopwords, handleSevenElevenCatalog, handleLottecinemaTheaters, handleLottecinemaMovies, handleLottecinemaSeats, } from './cli/commands/convenience.js';
|
|
15
|
+
import { handleCuStores, handleCuInventory, handleEmart24Stores, handleEmart24Products, handleEmart24Inventory, handleLotteMartStores, handleLotteMartProducts, handlePlaces, handleGs25Stores, handleGs25Products, handleGs25Inventory, handleSevenElevenProducts, handleSevenElevenStores, handleSevenElevenPopwords, handleSevenElevenCatalog, handleLottecinemaTheaters, handleLottecinemaMovies, handleLottecinemaSeats, } from './cli/commands/convenience.js';
|
|
16
|
+
function rejectTopLevelUnknownOptions(options, command, deps) {
|
|
17
|
+
const parsed = parseCliArgs(options);
|
|
18
|
+
return validateCommandOptions(command, parsed.options, deps.writeErr);
|
|
19
|
+
}
|
|
14
20
|
export async function runCli(argv, deps) {
|
|
15
21
|
const resolvedDeps = {
|
|
16
22
|
...createDefaultDeps(),
|
|
@@ -39,14 +45,23 @@ export async function runCli(argv, deps) {
|
|
|
39
45
|
return 0;
|
|
40
46
|
}
|
|
41
47
|
if (command === 'version' || command === '--version' || command === '-v') {
|
|
48
|
+
if (rejectTopLevelUnknownOptions(options, 'version', resolvedDeps)) {
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
42
51
|
resolvedDeps.writeOut(resolvedDeps.getVersion());
|
|
43
52
|
return 0;
|
|
44
53
|
}
|
|
45
54
|
if (command === 'url') {
|
|
55
|
+
if (rejectTopLevelUnknownOptions(options, 'url', resolvedDeps)) {
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
46
58
|
resolvedDeps.writeOut(DEFAULT_MCP_URL);
|
|
47
59
|
return 0;
|
|
48
60
|
}
|
|
49
61
|
if (command === 'health') {
|
|
62
|
+
if (rejectTopLevelUnknownOptions(options, 'health', resolvedDeps)) {
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
50
65
|
try {
|
|
51
66
|
const response = await resolvedDeps.fetchImpl(`${DEFAULT_BASE_URL}/health`);
|
|
52
67
|
if (!response.ok) {
|
|
@@ -68,6 +83,9 @@ export async function runCli(argv, deps) {
|
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
if (command === 'claude') {
|
|
86
|
+
if (rejectTopLevelUnknownOptions(options, 'claude', resolvedDeps)) {
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
71
89
|
const cliArgs = ['mcp', 'add', 'daiso', DEFAULT_BASE_URL, '--transport', 'sse'];
|
|
72
90
|
if (options.includes('--exec')) {
|
|
73
91
|
try {
|
|
@@ -94,6 +112,8 @@ export async function runCli(argv, deps) {
|
|
|
94
112
|
return await handleInventory(options, resolvedDeps);
|
|
95
113
|
if (command === 'display-location')
|
|
96
114
|
return await handleDisplayLocation(options, resolvedDeps);
|
|
115
|
+
if (command === 'places')
|
|
116
|
+
return await handlePlaces(options, resolvedDeps);
|
|
97
117
|
if (command === 'cu-stores')
|
|
98
118
|
return await handleCuStores(options, resolvedDeps);
|
|
99
119
|
if (command === 'cu-inventory')
|
|
@@ -129,6 +149,10 @@ export async function runCli(argv, deps) {
|
|
|
129
149
|
if (command === 'seveneleven-catalog')
|
|
130
150
|
return await handleSevenElevenCatalog(options, resolvedDeps);
|
|
131
151
|
resolvedDeps.writeErr(`알 수 없는 명령어: ${command}`);
|
|
152
|
+
if (command === 'search') {
|
|
153
|
+
const query = options[0] ?? '<검색어>';
|
|
154
|
+
resolvedDeps.writeErr(`혹시 찾으신 명령: daiso products ${query}`);
|
|
155
|
+
}
|
|
132
156
|
resolvedDeps.writeErr('도움말: daiso help');
|
|
133
157
|
return 1;
|
|
134
158
|
}
|
package/dist/cliHelp.js
CHANGED
|
@@ -13,6 +13,7 @@ export const COMMAND_LIST = [
|
|
|
13
13
|
'stores',
|
|
14
14
|
'inventory',
|
|
15
15
|
'display-location',
|
|
16
|
+
'places',
|
|
16
17
|
'cu-stores',
|
|
17
18
|
'cu-inventory',
|
|
18
19
|
'lottecinema-theaters',
|
|
@@ -43,6 +44,7 @@ const COMMAND_SUMMARY = {
|
|
|
43
44
|
stores: '다이소 매장 검색',
|
|
44
45
|
inventory: '다이소 재고 조회',
|
|
45
46
|
'display-location': '다이소 진열 위치 조회',
|
|
47
|
+
places: '음식점/카페 등 주변 장소 검색',
|
|
46
48
|
'cu-stores': 'CU 매장 검색',
|
|
47
49
|
'cu-inventory': 'CU 재고 조회',
|
|
48
50
|
'lottecinema-theaters': '롯데시네마 주변 지점 조회',
|
|
@@ -68,11 +70,7 @@ const COMMAND_DETAIL = {
|
|
|
68
70
|
'사용법: daiso help [command]',
|
|
69
71
|
'예시: daiso help products',
|
|
70
72
|
],
|
|
71
|
-
version: [
|
|
72
|
-
'명령: version',
|
|
73
|
-
'설명: 설치된 daiso CLI 버전을 출력합니다.',
|
|
74
|
-
'사용법: daiso version',
|
|
75
|
-
],
|
|
73
|
+
version: ['명령: version', '설명: 설치된 daiso CLI 버전을 출력합니다.', '사용법: daiso version'],
|
|
76
74
|
url: [
|
|
77
75
|
'명령: url',
|
|
78
76
|
'설명: MCP 엔드포인트 URL을 출력합니다.',
|
|
@@ -141,6 +139,15 @@ const COMMAND_DETAIL = {
|
|
|
141
139
|
'옵션: --json',
|
|
142
140
|
'예시: daiso display-location 1034604 04515',
|
|
143
141
|
],
|
|
142
|
+
places: [
|
|
143
|
+
'명령: places',
|
|
144
|
+
'설명: 네이버 지역 검색으로 음식점, 카페, 디저트 가게 등 주변 장소를 검색합니다.',
|
|
145
|
+
'사용법: daiso places <location> [--category cafe|restaurant|food|dessert|all] [--keyword 검색어] [--limit N] [--json]',
|
|
146
|
+
'필수: <location> 또는 --keyword',
|
|
147
|
+
'옵션: --category, --keyword, --limit, --start, --sort, --json',
|
|
148
|
+
'예시: daiso places 강남역 --category cafe --limit 5',
|
|
149
|
+
'예시: daiso places 성수동 --keyword 브런치 --limit 5',
|
|
150
|
+
],
|
|
144
151
|
'cu-stores': [
|
|
145
152
|
'명령: cu-stores',
|
|
146
153
|
'설명: CU 매장 검색 API를 호출합니다.',
|
|
@@ -304,6 +311,7 @@ export function printHelp(writeOut) {
|
|
|
304
311
|
writeOut(' npx daiso stores 강남역');
|
|
305
312
|
writeOut(' npx daiso inventory 1034604 --keyword 강남역');
|
|
306
313
|
writeOut(' npx daiso display-location 1034604 04515');
|
|
314
|
+
writeOut(' npx daiso places 강남역 --category cafe --limit 5');
|
|
307
315
|
writeOut(' npx daiso cu-stores 강남');
|
|
308
316
|
writeOut(' npx daiso cu-inventory 과자 --storeKeyword 강남');
|
|
309
317
|
writeOut(' npx daiso lottecinema-theaters --keyword "안산 중앙역" --limit 5');
|
|
@@ -323,13 +331,19 @@ export function printHelp(writeOut) {
|
|
|
323
331
|
writeOut(' npx daiso seveneleven-catalog --limit 10');
|
|
324
332
|
writeOut(' npx daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056');
|
|
325
333
|
writeOut('');
|
|
334
|
+
writeOut('정보가 부족할 때:');
|
|
335
|
+
writeOut(' 제품명을 먼저 검색해 productId를 확인: npx daiso products 수납박스');
|
|
336
|
+
writeOut(' productId로 재고 확인: npx daiso inventory <productId> --keyword 강남역');
|
|
337
|
+
writeOut(' 매장을 먼저 검색해 storeName 또는 storeCode를 확인: npx daiso stores 강남역');
|
|
338
|
+
writeOut(' 롯데마트 매장 확인: npx daiso lottemart-stores 잠실 --area 서울');
|
|
339
|
+
writeOut('');
|
|
326
340
|
writeOut('상세 도움말:');
|
|
327
341
|
writeOut(' npx daiso help <command>');
|
|
328
342
|
}
|
|
329
343
|
export function printCommandHelp(command, writeOut, writeErr) {
|
|
330
344
|
if (!Object.hasOwn(COMMAND_DETAIL, command)) {
|
|
331
345
|
writeErr(`도움말을 찾을 수 없는 명령어: ${command}`);
|
|
332
|
-
writeErr('사용 가능한 명령어: help, version, url, health, claude, get, products, product, stores, inventory, display-location, cu-stores, cu-inventory, lottecinema-theaters, lottecinema-movies, lottecinema-seats, lottemart-stores, lottemart-products, emart24-stores, emart24-products, emart24-inventory, gs25-stores, gs25-products, gs25-inventory, seveneleven-products, seveneleven-stores, seveneleven-popwords, seveneleven-catalog');
|
|
346
|
+
writeErr('사용 가능한 명령어: help, version, url, health, claude, get, products, product, stores, inventory, display-location, places, cu-stores, cu-inventory, lottecinema-theaters, lottecinema-movies, lottecinema-seats, lottemart-stores, lottemart-products, emart24-stores, emart24-products, emart24-inventory, gs25-stores, gs25-products, gs25-inventory, seveneleven-products, seveneleven-stores, seveneleven-popwords, seveneleven-catalog');
|
|
333
347
|
return 1;
|
|
334
348
|
}
|
|
335
349
|
const detail = COMMAND_DETAIL[command];
|