daiso 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +249 -533
  2. package/dist/api/actionsProxy.js +82 -0
  3. package/dist/api/cgvHandlers.js +103 -16
  4. package/dist/api/configStatus.js +19 -0
  5. package/dist/api/daisoHandlers.js +127 -0
  6. package/dist/api/emart24Handlers.js +176 -0
  7. package/dist/api/gs25Handlers.js +267 -0
  8. package/dist/api/handlers.js +209 -104
  9. package/dist/api/healthChecks.js +504 -0
  10. package/dist/api/lottecinemaHandlers.js +155 -0
  11. package/dist/api/lottemartHandlers.js +144 -0
  12. package/dist/api/megaboxHandlers.js +81 -50
  13. package/dist/api/response.js +2 -0
  14. package/dist/api/routes/cuRoutes.js +17 -0
  15. package/dist/api/routes/daisoRoutes.js +6 -1
  16. package/dist/api/routes/emart24Routes.js +22 -0
  17. package/dist/api/routes/gs25Routes.js +22 -0
  18. package/dist/api/routes/healthRoutes.js +61 -0
  19. package/dist/api/routes/lottecinemaRoutes.js +22 -0
  20. package/dist/api/routes/lottemartRoutes.js +18 -0
  21. package/dist/api/routes/oliveyoungRoutes.js +6 -1
  22. package/dist/api/routes/sevenelevenRoutes.js +32 -0
  23. package/dist/api/sevenelevenHandlers.js +200 -0
  24. package/dist/bin.js +0 -0
  25. package/dist/cli/args.js +75 -0
  26. package/dist/cli/commandOptions.js +38 -0
  27. package/dist/cli/commands/convenience.js +293 -0
  28. package/dist/cli/commands/daiso.js +112 -0
  29. package/dist/cli/constants.js +5 -0
  30. package/dist/cli/deps.js +48 -0
  31. package/dist/cli/http.js +156 -0
  32. package/dist/cli/interactiveFetch.js +31 -0
  33. package/dist/cli/interactiveItemSearch.js +261 -0
  34. package/dist/cli/interactivePrompt.js +50 -0
  35. package/dist/cli/interactiveTypes.js +4 -0
  36. package/dist/cli/types.js +4 -0
  37. package/dist/cli.js +87 -196
  38. package/dist/cliHelp.js +212 -1
  39. package/dist/cliInteractive.js +243 -0
  40. package/dist/cliPicker.js +57 -0
  41. package/dist/cliRenderer.js +90 -3
  42. package/dist/core/errors.js +66 -0
  43. package/dist/core/registry.js +227 -3
  44. package/dist/index.js +149 -9
  45. package/dist/pages/openapi.js +2 -1
  46. package/dist/pages/openapiFullSpec.js +58 -0
  47. package/dist/pages/openapiResponse.js +28 -0
  48. package/dist/pages/openapiSpec.js +2 -41
  49. package/dist/pages/openapiSpecActions.js +109 -0
  50. package/dist/pages/openapiSpecComponents.js +30 -391
  51. package/dist/pages/openapiSpecComponentsCgv.js +24 -0
  52. package/dist/pages/openapiSpecComponentsCu.js +112 -0
  53. package/dist/pages/openapiSpecComponentsDaisoOliveyoung.js +342 -0
  54. package/dist/pages/openapiSpecComponentsEmart24.js +141 -0
  55. package/dist/pages/openapiSpecComponentsGs25.js +109 -0
  56. package/dist/pages/openapiSpecComponentsLotteMart.js +85 -0
  57. package/dist/pages/openapiSpecComponentsLottecinema.js +163 -0
  58. package/dist/pages/openapiSpecComponentsMegabox.js +144 -0
  59. package/dist/pages/openapiSpecComponentsSeveneleven.js +82 -0
  60. package/dist/pages/openapiSpecPathsCgv.js +66 -3
  61. package/dist/pages/openapiSpecPathsCu.js +145 -0
  62. package/dist/pages/openapiSpecPathsDaiso.js +302 -0
  63. package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +4 -376
  64. package/dist/pages/openapiSpecPathsEmart24.js +223 -0
  65. package/dist/pages/openapiSpecPathsGs25.js +166 -0
  66. package/dist/pages/openapiSpecPathsLotteMart.js +149 -0
  67. package/dist/pages/openapiSpecPathsLottecinema.js +212 -0
  68. package/dist/pages/openapiSpecPathsMegabox.js +52 -3
  69. package/dist/pages/openapiSpecPathsOliveyoung.js +266 -0
  70. package/dist/pages/openapiSpecPathsSeveneleven.js +234 -0
  71. package/dist/pages/prompt.js +8 -402
  72. package/dist/pages/promptCoreText.js +278 -0
  73. package/dist/pages/promptReferenceText.js +137 -0
  74. package/dist/pages/promptRetailCinemaText.js +274 -0
  75. package/dist/services/cgv/client.js +6 -1
  76. package/dist/services/cgv/index.js +3 -3
  77. package/dist/services/cgv/location.js +235 -0
  78. package/dist/services/cgv/tools/findTheaters.js +30 -5
  79. package/dist/services/cgv/tools/getTimetable.js +42 -14
  80. package/dist/services/cgv/tools/searchMovies.js +40 -12
  81. package/dist/services/cgv/transport.js +0 -3
  82. package/dist/services/cu/api.js +11 -0
  83. package/dist/services/cu/client.js +297 -0
  84. package/dist/services/cu/index.js +21 -0
  85. package/dist/services/cu/tools/checkInventory.js +123 -0
  86. package/dist/services/cu/tools/findNearbyStores.js +49 -0
  87. package/dist/services/cu/types.js +4 -0
  88. package/dist/services/daiso/api.js +22 -2
  89. package/dist/services/daiso/client.js +67 -0
  90. package/dist/services/daiso/index.js +4 -0
  91. package/dist/services/daiso/product.js +17 -0
  92. package/dist/services/daiso/tools/checkInventory.js +80 -43
  93. package/dist/services/daiso/tools/findInventoryByName.js +222 -0
  94. package/dist/services/daiso/tools/findStores.js +17 -7
  95. package/dist/services/daiso/tools/getDisplayLocation.js +75 -0
  96. package/dist/services/emart24/api.js +11 -0
  97. package/dist/services/emart24/client.js +182 -0
  98. package/dist/services/emart24/index.js +22 -0
  99. package/dist/services/emart24/inventoryLookup.js +134 -0
  100. package/dist/services/emart24/tools/checkInventory.js +54 -0
  101. package/dist/services/emart24/tools/findNearbyStores.js +72 -0
  102. package/dist/services/emart24/tools/searchProducts.js +73 -0
  103. package/dist/services/emart24/types.js +4 -0
  104. package/dist/services/gs25/api.js +9 -0
  105. package/dist/services/gs25/client.js +208 -0
  106. package/dist/services/gs25/index.js +22 -0
  107. package/dist/services/gs25/storeUtils.js +168 -0
  108. package/dist/services/gs25/tools/checkInventory.js +163 -0
  109. package/dist/services/gs25/tools/findNearbyStores.js +103 -0
  110. package/dist/services/gs25/tools/searchProducts.js +57 -0
  111. package/dist/services/gs25/types.js +4 -0
  112. package/dist/services/lottecinema/api.js +12 -0
  113. package/dist/services/lottecinema/client.js +161 -0
  114. package/dist/services/lottecinema/index.js +30 -0
  115. package/dist/services/lottecinema/location.js +191 -0
  116. package/dist/services/lottecinema/tools/findNearbyTheaters.js +47 -0
  117. package/dist/services/lottecinema/tools/getRemainingSeats.js +85 -0
  118. package/dist/services/lottecinema/tools/listNowShowing.js +79 -0
  119. package/dist/services/lottecinema/types.js +4 -0
  120. package/dist/services/lottemart/api.js +31 -0
  121. package/dist/services/lottemart/client.js +254 -0
  122. package/dist/services/lottemart/clientTypes.js +4 -0
  123. package/dist/services/lottemart/config.js +4 -0
  124. package/dist/services/lottemart/debug.js +92 -0
  125. package/dist/services/lottemart/geocode.js +49 -0
  126. package/dist/services/lottemart/index.js +30 -0
  127. package/dist/services/lottemart/keyword.js +29 -0
  128. package/dist/services/lottemart/parser.js +185 -0
  129. package/dist/services/lottemart/session.js +331 -0
  130. package/dist/services/lottemart/storeSearch.js +52 -0
  131. package/dist/services/lottemart/tools/findNearbyStores.js +67 -0
  132. package/dist/services/lottemart/tools/searchProducts.js +69 -0
  133. package/dist/services/lottemart/types.js +4 -0
  134. package/dist/services/lottemart/zetta.js +105 -0
  135. package/dist/services/megabox/location.js +219 -0
  136. package/dist/services/megabox/tools/findNearbyTheaters.js +13 -44
  137. package/dist/services/megabox/tools/getRemainingSeats.js +31 -3
  138. package/dist/services/megabox/tools/listNowShowing.js +31 -3
  139. package/dist/services/oliveyoung/api.js +2 -0
  140. package/dist/services/oliveyoung/client.js +225 -13
  141. package/dist/services/oliveyoung/index.js +3 -1
  142. package/dist/services/oliveyoung/tools/checkInventory.js +21 -5
  143. package/dist/services/oliveyoung/tools/searchProducts.js +57 -0
  144. package/dist/services/seveneleven/api.js +15 -0
  145. package/dist/services/seveneleven/client.js +286 -0
  146. package/dist/services/seveneleven/index.js +30 -0
  147. package/dist/services/seveneleven/inventory.js +302 -0
  148. package/dist/services/seveneleven/productKeyword.js +141 -0
  149. package/dist/services/seveneleven/tools/checkInventory.js +82 -0
  150. package/dist/services/seveneleven/tools/getCatalogSnapshot.js +55 -0
  151. package/dist/services/seveneleven/tools/getSearchPopwords.js +41 -0
  152. package/dist/services/seveneleven/tools/searchProducts.js +67 -0
  153. package/dist/services/seveneleven/tools/searchStores.js +45 -0
  154. package/dist/services/seveneleven/types.js +4 -0
  155. package/dist/utils/cliInteractiveHelpers.js +63 -0
  156. package/dist/utils/daisoKeyword.js +30 -0
  157. package/dist/utils/http.js +98 -12
  158. package/dist/utils/zyte.js +47 -26
  159. package/openapi.json +1979 -736
  160. package/openapi.yaml +1428 -541
  161. package/package.json +49 -18
  162. package/dist/tools/checkInventory.js +0 -98
  163. package/dist/tools/findStores.js +0 -125
  164. package/dist/tools/getPriceInfo.js +0 -77
  165. package/dist/tools/searchProducts.js +0 -61
  166. package/dist/types/index.js +0 -4
@@ -0,0 +1,200 @@
1
+ /**
2
+ * 세븐일레븐 GET API 핸들러
3
+ */
4
+ /* c8 ignore start */
5
+ import { errorResponse, successResponse } from './response.js';
6
+ import { fetchSevenElevenStoresByKeyword, fetchSevenElevenCatalogSnapshot, fetchSevenElevenSearchPopwords, searchSevenElevenProducts, } from '../services/seveneleven/client.js';
7
+ import { checkSevenElevenInventory } from '../services/seveneleven/inventory.js';
8
+ function parseBoolean(value, defaultValue) {
9
+ if (typeof value !== 'string') {
10
+ return defaultValue;
11
+ }
12
+ const normalized = value.trim().toLowerCase();
13
+ if (normalized === 'true' || normalized === '1' || normalized === 'y') {
14
+ return true;
15
+ }
16
+ if (normalized === 'false' || normalized === '0' || normalized === 'n') {
17
+ return false;
18
+ }
19
+ return defaultValue;
20
+ }
21
+ /**
22
+ * 세븐일레븐 상품 검색 API 핸들러
23
+ * GET /api/seveneleven/products?query={검색어}&page={페이지}&size={개수}&sort={정렬}
24
+ */
25
+ export async function handleSevenElevenSearchProducts(c) {
26
+ const query = c.req.query('query') || '';
27
+ const page = parseInt(c.req.query('page') || '1', 10);
28
+ const size = parseInt(c.req.query('size') || '20', 10);
29
+ const sort = c.req.query('sort') || 'recommend';
30
+ if (query.trim().length === 0) {
31
+ return errorResponse(c, 'MISSING_QUERY', '검색어(query)를 입력해주세요.');
32
+ }
33
+ try {
34
+ const result = await searchSevenElevenProducts({
35
+ query,
36
+ page,
37
+ size,
38
+ sort,
39
+ });
40
+ return successResponse(c, {
41
+ query: result.query,
42
+ count: result.products.length,
43
+ totalCount: result.totalCount,
44
+ collectionIds: result.collectionIds,
45
+ products: result.products,
46
+ }, {
47
+ total: result.totalCount,
48
+ page,
49
+ pageSize: size,
50
+ });
51
+ }
52
+ catch (error) {
53
+ const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
54
+ return errorResponse(c, 'SEVENELEVEN_PRODUCT_SEARCH_FAILED', message, 500);
55
+ }
56
+ }
57
+ /**
58
+ * 세븐일레븐 매장 검색 API 핸들러
59
+ * GET /api/seveneleven/stores?keyword={검색어}&limit={개수}
60
+ */
61
+ export async function handleSevenElevenSearchStores(c) {
62
+ const keyword = c.req.query('keyword') || '';
63
+ const limit = parseInt(c.req.query('limit') || '20', 10);
64
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : 20;
65
+ if (keyword.trim().length === 0) {
66
+ return errorResponse(c, 'MISSING_KEYWORD', '매장 검색어(keyword)를 입력해주세요.');
67
+ }
68
+ try {
69
+ const result = await fetchSevenElevenStoresByKeyword({
70
+ keyword,
71
+ limit: safeLimit,
72
+ });
73
+ return successResponse(c, {
74
+ keyword: result.query,
75
+ count: result.stores.length,
76
+ totalCount: result.totalCount,
77
+ stores: result.stores,
78
+ }, {
79
+ total: result.totalCount,
80
+ page: 1,
81
+ pageSize: safeLimit,
82
+ });
83
+ }
84
+ catch (error) {
85
+ const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
86
+ return errorResponse(c, 'SEVENELEVEN_STORE_SEARCH_FAILED', message, 500);
87
+ }
88
+ }
89
+ /**
90
+ * 세븐일레븐 재고 확인 API 핸들러
91
+ * GET /api/seveneleven/inventory?keyword={검색어}&storeKeyword={매장키워드}
92
+ */
93
+ export async function handleSevenElevenCheckInventory(c) {
94
+ const keyword = c.req.query('keyword') || '';
95
+ const storeKeyword = c.req.query('storeKeyword') || '';
96
+ const storeLimit = parseInt(c.req.query('storeLimit') || '20', 10);
97
+ const timeoutMs = parseInt(c.req.query('timeoutMs') || '20000', 10);
98
+ const safeStoreLimit = Number.isFinite(storeLimit) && storeLimit > 0 ? storeLimit : 20;
99
+ const safeTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 20000;
100
+ if (keyword.trim().length === 0) {
101
+ return errorResponse(c, 'MISSING_QUERY', '검색어(keyword)를 입력해주세요.');
102
+ }
103
+ try {
104
+ const result = await checkSevenElevenInventory({
105
+ productKeyword: keyword,
106
+ storeKeyword,
107
+ storeLimit: safeStoreLimit,
108
+ }, {
109
+ timeout: safeTimeoutMs,
110
+ });
111
+ const note = result.stockAvailable
112
+ ? '실시간 재고 데이터가 포함되어 있습니다.'
113
+ : result.stockError
114
+ ? `실시간 재고 API 호출에 실패했습니다: ${result.stockError.message}`
115
+ : '실시간 재고 API가 현재 제한되어 있어 매장 목록만 제공됩니다.';
116
+ return successResponse(c, {
117
+ keyword,
118
+ storeKeyword,
119
+ product: result.product,
120
+ stockAvailable: result.stockAvailable,
121
+ stockError: result.stockError,
122
+ note,
123
+ inventory: {
124
+ totalStoreCount: result.totalStoreCount,
125
+ inStockStoreCount: result.inStockStoreCount,
126
+ count: result.stores.length,
127
+ stores: result.stores,
128
+ },
129
+ }, {
130
+ total: result.totalStoreCount,
131
+ pageSize: safeStoreLimit,
132
+ });
133
+ }
134
+ catch (error) {
135
+ const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
136
+ return errorResponse(c, 'SEVENELEVEN_INVENTORY_CHECK_FAILED', message, 500);
137
+ }
138
+ }
139
+ /**
140
+ * 세븐일레븐 인기 검색어 API 핸들러
141
+ * GET /api/seveneleven/popwords?label={라벨}
142
+ */
143
+ export async function handleSevenElevenGetSearchPopwords(c) {
144
+ const label = c.req.query('label') || 'home';
145
+ try {
146
+ const keywords = await fetchSevenElevenSearchPopwords(label);
147
+ return successResponse(c, {
148
+ label,
149
+ available: keywords.length > 0,
150
+ count: keywords.length,
151
+ keywords,
152
+ note: keywords.length === 0
153
+ ? '현재 응답에서 인기 검색어 목록을 찾지 못했습니다.'
154
+ : '홈 인기 검색어를 조회했습니다.',
155
+ });
156
+ }
157
+ catch (error) {
158
+ const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
159
+ return errorResponse(c, 'SEVENELEVEN_POPWORD_FETCH_FAILED', message, 500);
160
+ }
161
+ }
162
+ /**
163
+ * 세븐일레븐 카탈로그 스냅샷 API 핸들러
164
+ * GET /api/seveneleven/catalog?includeIssues={bool}&includeExhibition={bool}&limit={개수}
165
+ */
166
+ export async function handleSevenElevenGetCatalogSnapshot(c) {
167
+ const includeIssues = parseBoolean(c.req.query('includeIssues'), true);
168
+ const includeExhibition = parseBoolean(c.req.query('includeExhibition'), true);
169
+ const limit = parseInt(c.req.query('limit') || '20', 10);
170
+ try {
171
+ const result = await fetchSevenElevenCatalogSnapshot({
172
+ includeIssues,
173
+ includeExhibition,
174
+ });
175
+ return successResponse(c, {
176
+ options: {
177
+ includeIssues,
178
+ includeExhibition,
179
+ limit,
180
+ },
181
+ pages: {
182
+ totalCount: result.pages.length,
183
+ items: result.pages.slice(0, limit),
184
+ },
185
+ issues: {
186
+ totalCount: result.issues.length,
187
+ items: result.issues.slice(0, limit),
188
+ },
189
+ exhibitions: {
190
+ totalCount: result.exhibitions.length,
191
+ items: result.exhibitions.slice(0, limit),
192
+ },
193
+ });
194
+ }
195
+ catch (error) {
196
+ const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
197
+ return errorResponse(c, 'SEVENELEVEN_CATALOG_FETCH_FAILED', message, 500);
198
+ }
199
+ }
200
+ /* c8 ignore stop */
package/dist/bin.js CHANGED
File without changes
@@ -0,0 +1,75 @@
1
+ /**
2
+ * CLI 인자 파싱 및 URL 조작 유틸리티
3
+ */
4
+ import { DEFAULT_BASE_URL } from './constants.js';
5
+ export function parseCliArgs(args) {
6
+ const positionals = [];
7
+ const options = {};
8
+ for (let index = 0; index < args.length; index += 1) {
9
+ const token = args[index];
10
+ if (!token.startsWith('--')) {
11
+ positionals.push(token);
12
+ continue;
13
+ }
14
+ const withoutPrefix = token.slice(2);
15
+ const equalIndex = withoutPrefix.indexOf('=');
16
+ if (equalIndex >= 0) {
17
+ const key = withoutPrefix.slice(0, equalIndex);
18
+ const value = withoutPrefix.slice(equalIndex + 1);
19
+ options[key] = value;
20
+ continue;
21
+ }
22
+ const key = withoutPrefix;
23
+ const nextValue = args[index + 1];
24
+ if (!nextValue || nextValue.startsWith('--')) {
25
+ options[key] = 'true';
26
+ continue;
27
+ }
28
+ options[key] = nextValue;
29
+ index += 1;
30
+ }
31
+ return { positionals, options };
32
+ }
33
+ export function toUrl(pathOrUrl) {
34
+ if (pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')) {
35
+ return new URL(pathOrUrl);
36
+ }
37
+ const normalizedPath = pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`;
38
+ return new URL(normalizedPath, DEFAULT_BASE_URL);
39
+ }
40
+ export function applyOptionsToQuery(url, options) {
41
+ for (const [key, value] of Object.entries(options)) {
42
+ url.searchParams.set(key, value);
43
+ }
44
+ }
45
+ export function toQueryOptions(options) {
46
+ const queryOptions = {};
47
+ for (const [key, value] of Object.entries(options)) {
48
+ if (key === 'help' || key === 'json') {
49
+ continue;
50
+ }
51
+ queryOptions[key] = value;
52
+ }
53
+ return queryOptions;
54
+ }
55
+ export function findUnknownOption(options, allowedOptions) {
56
+ const allowed = new Set(allowedOptions);
57
+ for (const key of Object.keys(options)) {
58
+ if (!allowed.has(key)) {
59
+ return key;
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ export function writeUnknownOptionError(options, allowedOptions, writeErr, hint) {
65
+ const unknownOption = findUnknownOption(options, allowedOptions);
66
+ if (!unknownOption) {
67
+ return false;
68
+ }
69
+ writeErr(`알 수 없는 옵션: --${unknownOption}`);
70
+ if (hint) {
71
+ writeErr(hint);
72
+ }
73
+ writeErr('도움말: daiso help');
74
+ return true;
75
+ }
@@ -0,0 +1,38 @@
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
+ 'cu-stores': ['help', 'json', 'keyword', 'lat', 'lng', 'limit'],
16
+ 'cu-inventory': ['help', 'json', 'storeKeyword', 'lat', 'lng', 'size', 'offset', 'searchSort', 'storeLimit'],
17
+ 'lottecinema-theaters': ['help', 'json', 'keyword', 'lat', 'lng', 'playDate', 'limit'],
18
+ 'lottecinema-movies': ['help', 'json', 'keyword', 'playDate', 'theaterId', 'movieId', 'lat', 'lng'],
19
+ 'lottecinema-seats': ['help', 'json', 'keyword', 'playDate', 'theaterId', 'movieId', 'lat', 'lng', 'limit'],
20
+ 'emart24-stores': ['help', 'json', 'keyword', 'area1', 'area2', 'lat', 'lng', 'service24h', 'limit'],
21
+ 'emart24-products': ['help', 'json', 'page', 'pageSize'],
22
+ 'emart24-inventory': ['help', 'json', 'bizNoArr', 'storeKeyword', 'area1', 'area2', 'lat', 'lng', 'storeLimit'],
23
+ 'lottemart-stores': ['help', 'json', 'keyword', 'area', 'brandVariant', 'lat', 'lng', 'limit'],
24
+ 'lottemart-products': ['help', 'json', 'storeCode', 'storeName', 'area', 'brandVariant', 'pageLimit'],
25
+ 'gs25-stores': ['help', 'json', 'keyword', 'lat', 'lng', 'serviceCode', 'limit'],
26
+ 'gs25-products': ['help', 'json', 'serviceCode', 'limit'],
27
+ 'gs25-inventory': ['help', 'json', 'storeKeyword', 'lat', 'lng', 'serviceCode', 'storeLimit'],
28
+ 'seveneleven-products': ['help', 'json', 'page', 'size', 'sort'],
29
+ 'seveneleven-stores': ['help', 'json', 'limit'],
30
+ 'seveneleven-popwords': ['help', 'json', 'label'],
31
+ 'seveneleven-catalog': ['help', 'json', 'includeIssues', 'includeExhibition', 'limit'],
32
+ };
33
+ const COMMAND_OPTION_HINTS = {
34
+ inventory: '매장명은 --keyword로 전달하세요. 예: daiso inventory 1034604 --keyword 강남역',
35
+ };
36
+ export function validateCommandOptions(command, options, writeErr) {
37
+ return writeUnknownOptionError(options, COMMAND_OPTION_ALLOWLISTS[command], writeErr, COMMAND_OPTION_HINTS[command]);
38
+ }
@@ -0,0 +1,293 @@
1
+ /**
2
+ * 편의점 및 기타 서비스 CLI 명령 핸들러
3
+ * (CU, 이마트24, 롯데마트, GS25, 세븐일레븐, 롯데시네마)
4
+ */
5
+ import { printCommandHelp } from '../../cliHelp.js';
6
+ import { parseCliArgs, toUrl, applyOptionsToQuery, toQueryOptions } from '../args.js';
7
+ import { validateCommandOptions } from '../commandOptions.js';
8
+ import { requestAndPrintResponse } from '../http.js';
9
+ export async function handleCuStores(options, deps) {
10
+ const parsed = parseCliArgs(options);
11
+ if (parsed.options.help === 'true') {
12
+ return printCommandHelp('cu-stores', deps.writeOut, deps.writeErr);
13
+ }
14
+ const keyword = parsed.positionals[0];
15
+ if (keyword) {
16
+ parsed.options.keyword = keyword;
17
+ }
18
+ if (validateCommandOptions('cu-stores', parsed.options, deps.writeErr)) {
19
+ return 1;
20
+ }
21
+ const targetUrl = toUrl('/api/cu/stores');
22
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
23
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'cu-stores', parsed.options.json === 'true');
24
+ }
25
+ export async function handleCuInventory(options, deps) {
26
+ const parsed = parseCliArgs(options);
27
+ if (parsed.options.help === 'true') {
28
+ return printCommandHelp('cu-inventory', deps.writeOut, deps.writeErr);
29
+ }
30
+ if (validateCommandOptions('cu-inventory', parsed.options, deps.writeErr)) {
31
+ return 1;
32
+ }
33
+ const keyword = parsed.positionals[0];
34
+ if (!keyword) {
35
+ deps.writeErr('cu-inventory 명령은 검색어가 필요합니다. 예: daiso cu-inventory 과자');
36
+ return 1;
37
+ }
38
+ const targetUrl = toUrl('/api/cu/inventory');
39
+ targetUrl.searchParams.set('keyword', keyword);
40
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
41
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'cu-inventory', parsed.options.json === 'true');
42
+ }
43
+ export async function handleEmart24Stores(options, deps) {
44
+ const parsed = parseCliArgs(options);
45
+ if (parsed.options.help === 'true') {
46
+ return printCommandHelp('emart24-stores', deps.writeOut, deps.writeErr);
47
+ }
48
+ const keyword = parsed.positionals[0];
49
+ if (keyword) {
50
+ parsed.options.keyword = keyword;
51
+ }
52
+ if (validateCommandOptions('emart24-stores', parsed.options, deps.writeErr)) {
53
+ return 1;
54
+ }
55
+ const targetUrl = toUrl('/api/emart24/stores');
56
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
57
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'emart24-stores', parsed.options.json === 'true');
58
+ }
59
+ export async function handleEmart24Products(options, deps) {
60
+ const parsed = parseCliArgs(options);
61
+ if (parsed.options.help === 'true') {
62
+ return printCommandHelp('emart24-products', deps.writeOut, deps.writeErr);
63
+ }
64
+ if (validateCommandOptions('emart24-products', parsed.options, deps.writeErr)) {
65
+ return 1;
66
+ }
67
+ const keyword = parsed.positionals[0];
68
+ if (!keyword) {
69
+ deps.writeErr('emart24-products 명령은 검색어가 필요합니다. 예: daiso emart24-products 두바이');
70
+ return 1;
71
+ }
72
+ const targetUrl = toUrl('/api/emart24/products');
73
+ targetUrl.searchParams.set('keyword', keyword);
74
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
75
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'emart24-products', parsed.options.json === 'true');
76
+ }
77
+ export async function handleEmart24Inventory(options, deps) {
78
+ const parsed = parseCliArgs(options);
79
+ if (parsed.options.help === 'true') {
80
+ return printCommandHelp('emart24-inventory', deps.writeOut, deps.writeErr);
81
+ }
82
+ if (validateCommandOptions('emart24-inventory', parsed.options, deps.writeErr)) {
83
+ return 1;
84
+ }
85
+ const pluCd = parsed.positionals[0];
86
+ const bizNoArr = parsed.options.bizNoArr;
87
+ if (!pluCd || !bizNoArr) {
88
+ deps.writeErr('emart24-inventory 명령은 pluCd와 --bizNoArr가 필요합니다. 예: daiso emart24-inventory 8800244010504 --bizNoArr 28339,05015');
89
+ return 1;
90
+ }
91
+ const targetUrl = toUrl('/api/emart24/inventory');
92
+ targetUrl.searchParams.set('pluCd', pluCd);
93
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
94
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'emart24-inventory', parsed.options.json === 'true');
95
+ }
96
+ export async function handleLotteMartStores(options, deps) {
97
+ const parsed = parseCliArgs(options);
98
+ if (parsed.options.help === 'true') {
99
+ return printCommandHelp('lottemart-stores', deps.writeOut, deps.writeErr);
100
+ }
101
+ const keyword = parsed.positionals[0];
102
+ if (keyword) {
103
+ parsed.options.keyword = keyword;
104
+ }
105
+ if (validateCommandOptions('lottemart-stores', parsed.options, deps.writeErr)) {
106
+ return 1;
107
+ }
108
+ const targetUrl = toUrl('/api/lottemart/stores');
109
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
110
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottemart-stores', parsed.options.json === 'true');
111
+ }
112
+ export async function handleLotteMartProducts(options, deps) {
113
+ const parsed = parseCliArgs(options);
114
+ if (parsed.options.help === 'true') {
115
+ return printCommandHelp('lottemart-products', deps.writeOut, deps.writeErr);
116
+ }
117
+ if (validateCommandOptions('lottemart-products', parsed.options, deps.writeErr)) {
118
+ return 1;
119
+ }
120
+ const keyword = parsed.positionals[0];
121
+ if (!keyword) {
122
+ deps.writeErr('lottemart-products 명령은 검색어가 필요합니다. 예: daiso lottemart-products 콜라 --storeName 강변점');
123
+ return 1;
124
+ }
125
+ if (!parsed.options.storeCode && !parsed.options.storeName) {
126
+ deps.writeErr('lottemart-products 명령은 --storeCode 또는 --storeName이 필요합니다. 매장을 모르면 먼저 daiso lottemart-stores 잠실 --area 서울 명령으로 storeName/storeCode를 확인하세요. 예: daiso lottemart-products 콜라 --storeName 강변점');
127
+ return 1;
128
+ }
129
+ const targetUrl = toUrl('/api/lottemart/products');
130
+ targetUrl.searchParams.set('keyword', keyword);
131
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
132
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottemart-products', parsed.options.json === 'true');
133
+ }
134
+ export async function handleGs25Stores(options, deps) {
135
+ const parsed = parseCliArgs(options);
136
+ if (parsed.options.help === 'true') {
137
+ return printCommandHelp('gs25-stores', deps.writeOut, deps.writeErr);
138
+ }
139
+ const keyword = parsed.positionals[0];
140
+ if (keyword) {
141
+ parsed.options.keyword = keyword;
142
+ }
143
+ if (validateCommandOptions('gs25-stores', parsed.options, deps.writeErr)) {
144
+ return 1;
145
+ }
146
+ const targetUrl = toUrl('/api/gs25/stores');
147
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
148
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'gs25-stores', parsed.options.json === 'true');
149
+ }
150
+ export async function handleGs25Products(options, deps) {
151
+ const parsed = parseCliArgs(options);
152
+ if (parsed.options.help === 'true') {
153
+ return printCommandHelp('gs25-products', deps.writeOut, deps.writeErr);
154
+ }
155
+ if (validateCommandOptions('gs25-products', parsed.options, deps.writeErr)) {
156
+ return 1;
157
+ }
158
+ const keyword = parsed.positionals[0];
159
+ if (!keyword) {
160
+ deps.writeErr('gs25-products 명령은 검색어가 필요합니다. 예: daiso gs25-products 오감자');
161
+ return 1;
162
+ }
163
+ const targetUrl = toUrl('/api/gs25/products');
164
+ targetUrl.searchParams.set('keyword', keyword);
165
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
166
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'gs25-products', parsed.options.json === 'true');
167
+ }
168
+ export async function handleGs25Inventory(options, deps) {
169
+ const parsed = parseCliArgs(options);
170
+ if (parsed.options.help === 'true') {
171
+ return printCommandHelp('gs25-inventory', deps.writeOut, deps.writeErr);
172
+ }
173
+ if (validateCommandOptions('gs25-inventory', parsed.options, deps.writeErr)) {
174
+ return 1;
175
+ }
176
+ const keyword = parsed.positionals[0];
177
+ if (!keyword) {
178
+ deps.writeErr('gs25-inventory 명령은 검색어가 필요합니다. 예: daiso gs25-inventory 오감자');
179
+ return 1;
180
+ }
181
+ const targetUrl = toUrl('/api/gs25/inventory');
182
+ targetUrl.searchParams.set('keyword', keyword);
183
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
184
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'gs25-inventory', parsed.options.json === 'true');
185
+ }
186
+ export async function handleSevenElevenProducts(options, deps) {
187
+ const parsed = parseCliArgs(options);
188
+ if (parsed.options.help === 'true') {
189
+ return printCommandHelp('seveneleven-products', deps.writeOut, deps.writeErr);
190
+ }
191
+ if (validateCommandOptions('seveneleven-products', parsed.options, deps.writeErr)) {
192
+ return 1;
193
+ }
194
+ const query = parsed.positionals[0];
195
+ if (!query) {
196
+ deps.writeErr('seveneleven-products 명령은 검색어가 필요합니다. 예: daiso seveneleven-products 삼각김밥');
197
+ return 1;
198
+ }
199
+ const targetUrl = toUrl('/api/seveneleven/products');
200
+ targetUrl.searchParams.set('query', query);
201
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
202
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-products', parsed.options.json === 'true');
203
+ }
204
+ export async function handleSevenElevenStores(options, deps) {
205
+ const parsed = parseCliArgs(options);
206
+ if (parsed.options.help === 'true') {
207
+ return printCommandHelp('seveneleven-stores', deps.writeOut, deps.writeErr);
208
+ }
209
+ if (validateCommandOptions('seveneleven-stores', parsed.options, deps.writeErr)) {
210
+ return 1;
211
+ }
212
+ const keyword = parsed.positionals[0];
213
+ if (!keyword) {
214
+ deps.writeErr('seveneleven-stores 명령은 검색어가 필요합니다. 예: daiso seveneleven-stores 안산 중앙역');
215
+ return 1;
216
+ }
217
+ const targetUrl = toUrl('/api/seveneleven/stores');
218
+ targetUrl.searchParams.set('keyword', keyword);
219
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
220
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-stores', parsed.options.json === 'true');
221
+ }
222
+ export async function handleSevenElevenPopwords(options, deps) {
223
+ const parsed = parseCliArgs(options);
224
+ if (parsed.options.help === 'true') {
225
+ return printCommandHelp('seveneleven-popwords', deps.writeOut, deps.writeErr);
226
+ }
227
+ if (validateCommandOptions('seveneleven-popwords', parsed.options, deps.writeErr)) {
228
+ return 1;
229
+ }
230
+ const targetUrl = toUrl('/api/seveneleven/popwords');
231
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
232
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-popwords', parsed.options.json === 'true');
233
+ }
234
+ export async function handleSevenElevenCatalog(options, deps) {
235
+ const parsed = parseCliArgs(options);
236
+ if (parsed.options.help === 'true') {
237
+ return printCommandHelp('seveneleven-catalog', deps.writeOut, deps.writeErr);
238
+ }
239
+ if (validateCommandOptions('seveneleven-catalog', parsed.options, deps.writeErr)) {
240
+ return 1;
241
+ }
242
+ const targetUrl = toUrl('/api/seveneleven/catalog');
243
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
244
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-catalog', parsed.options.json === 'true');
245
+ }
246
+ export async function handleLottecinemaTheaters(options, deps) {
247
+ const parsed = parseCliArgs(options);
248
+ if (parsed.options.help === 'true') {
249
+ return printCommandHelp('lottecinema-theaters', deps.writeOut, deps.writeErr);
250
+ }
251
+ const keyword = parsed.positionals[0];
252
+ if (keyword) {
253
+ parsed.options.keyword = keyword;
254
+ }
255
+ if (validateCommandOptions('lottecinema-theaters', parsed.options, deps.writeErr)) {
256
+ return 1;
257
+ }
258
+ const targetUrl = toUrl('/api/lottecinema/theaters');
259
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
260
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-theaters', parsed.options.json === 'true');
261
+ }
262
+ export async function handleLottecinemaMovies(options, deps) {
263
+ const parsed = parseCliArgs(options);
264
+ if (parsed.options.help === 'true') {
265
+ return printCommandHelp('lottecinema-movies', deps.writeOut, deps.writeErr);
266
+ }
267
+ const keyword = parsed.positionals[0];
268
+ if (keyword) {
269
+ parsed.options.keyword = keyword;
270
+ }
271
+ if (validateCommandOptions('lottecinema-movies', parsed.options, deps.writeErr)) {
272
+ return 1;
273
+ }
274
+ const targetUrl = toUrl('/api/lottecinema/movies');
275
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
276
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-movies', parsed.options.json === 'true');
277
+ }
278
+ export async function handleLottecinemaSeats(options, deps) {
279
+ const parsed = parseCliArgs(options);
280
+ if (parsed.options.help === 'true') {
281
+ return printCommandHelp('lottecinema-seats', deps.writeOut, deps.writeErr);
282
+ }
283
+ const keyword = parsed.positionals[0];
284
+ if (keyword) {
285
+ parsed.options.keyword = keyword;
286
+ }
287
+ if (validateCommandOptions('lottecinema-seats', parsed.options, deps.writeErr)) {
288
+ return 1;
289
+ }
290
+ const targetUrl = toUrl('/api/lottecinema/seats');
291
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
292
+ return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-seats', parsed.options.json === 'true');
293
+ }