daiso 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,6 +52,9 @@ npx daiso product 1034604
52
52
  npx daiso stores 강남역
53
53
  npx daiso inventory 1034604 --keyword 강남역
54
54
  npx daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056
55
+
56
+ # 원본 JSON 필요 시
57
+ npx daiso products 수납박스 --json
55
58
  ```
56
59
 
57
60
  <br>
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import path from 'node:path';
9
9
  import { fileURLToPath } from 'node:url';
10
10
  import { spawn } from 'node:child_process';
11
11
  import { printCommandHelp, printHelp } from './cliHelp.js';
12
+ import { renderApiEnvelope } from './cliRenderer.js';
12
13
  const DEFAULT_BASE_URL = 'https://mcp.aka.page';
13
14
  const DEFAULT_MCP_URL = `${DEFAULT_BASE_URL}/mcp`;
14
15
  function loadVersion() {
@@ -89,7 +90,17 @@ function applyOptionsToQuery(url, options) {
89
90
  url.searchParams.set(key, value);
90
91
  }
91
92
  }
92
- async function requestAndPrintJson(fetchImpl, writeOut, writeErr, url) {
93
+ function toQueryOptions(options) {
94
+ const queryOptions = {};
95
+ for (const [key, value] of Object.entries(options)) {
96
+ if (key === 'help' || key === 'json') {
97
+ continue;
98
+ }
99
+ queryOptions[key] = value;
100
+ }
101
+ return queryOptions;
102
+ }
103
+ async function requestAndPrintResponse(fetchImpl, writeOut, writeErr, url, command, asJson) {
93
104
  try {
94
105
  const response = await fetchImpl(url.toString());
95
106
  if (!response.ok) {
@@ -101,7 +112,12 @@ async function requestAndPrintJson(fetchImpl, writeOut, writeErr, url) {
101
112
  return 1;
102
113
  }
103
114
  const payload = (await response.json());
104
- writeOut(JSON.stringify(payload, null, 2));
115
+ if (asJson) {
116
+ writeOut(JSON.stringify(payload, null, 2));
117
+ }
118
+ else {
119
+ writeOut(renderApiEnvelope(command, url, payload));
120
+ }
105
121
  return 0;
106
122
  }
107
123
  catch (error) {
@@ -179,8 +195,8 @@ export async function runCli(argv, deps) {
179
195
  return 1;
180
196
  }
181
197
  const targetUrl = toUrl(targetPath);
182
- applyOptionsToQuery(targetUrl, parsed.options);
183
- return await requestAndPrintJson(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl);
198
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
199
+ return await requestAndPrintResponse(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl, command, parsed.options.json === 'true');
184
200
  }
185
201
  if (command === 'products') {
186
202
  const parsed = parseCliArgs(options);
@@ -194,8 +210,8 @@ export async function runCli(argv, deps) {
194
210
  }
195
211
  const targetUrl = toUrl('/api/daiso/products');
196
212
  targetUrl.searchParams.set('q', query);
197
- applyOptionsToQuery(targetUrl, parsed.options);
198
- return await requestAndPrintJson(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl);
213
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
214
+ return await requestAndPrintResponse(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl, command, parsed.options.json === 'true');
199
215
  }
200
216
  if (command === 'product') {
201
217
  const parsed = parseCliArgs(options);
@@ -208,8 +224,8 @@ export async function runCli(argv, deps) {
208
224
  return 1;
209
225
  }
210
226
  const targetUrl = toUrl(`/api/daiso/products/${productId}`);
211
- applyOptionsToQuery(targetUrl, parsed.options);
212
- return await requestAndPrintJson(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl);
227
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
228
+ return await requestAndPrintResponse(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl, command, parsed.options.json === 'true');
213
229
  }
214
230
  if (command === 'stores') {
215
231
  const parsed = parseCliArgs(options);
@@ -225,8 +241,8 @@ export async function runCli(argv, deps) {
225
241
  return 1;
226
242
  }
227
243
  const targetUrl = toUrl('/api/daiso/stores');
228
- applyOptionsToQuery(targetUrl, parsed.options);
229
- return await requestAndPrintJson(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl);
244
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
245
+ return await requestAndPrintResponse(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl, command, parsed.options.json === 'true');
230
246
  }
231
247
  if (command === 'inventory') {
232
248
  const parsed = parseCliArgs(options);
@@ -240,8 +256,8 @@ export async function runCli(argv, deps) {
240
256
  }
241
257
  const targetUrl = toUrl('/api/daiso/inventory');
242
258
  targetUrl.searchParams.set('productId', productId);
243
- applyOptionsToQuery(targetUrl, parsed.options);
244
- return await requestAndPrintJson(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl);
259
+ applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
260
+ return await requestAndPrintResponse(resolvedDeps.fetchImpl, resolvedDeps.writeOut, resolvedDeps.writeErr, targetUrl, command, parsed.options.json === 'true');
245
261
  }
246
262
  resolvedDeps.writeErr(`알 수 없는 명령어: ${command}`);
247
263
  resolvedDeps.writeErr('도움말: daiso help');
package/dist/cliHelp.js CHANGED
@@ -59,40 +59,42 @@ const COMMAND_DETAIL = {
59
59
  get: [
60
60
  '명령: get',
61
61
  '설명: 임의 API 경로를 GET으로 호출합니다.',
62
- '사용법: daiso get <path|url> [--queryKey value ...]',
62
+ '사용법: daiso get <path|url> [--queryKey value ...] [--json]',
63
63
  '필수: <path|url>',
64
+ '옵션: --json 원본 JSON 출력',
64
65
  '예시: daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056',
65
66
  ],
66
67
  products: [
67
68
  '명령: products',
68
69
  '설명: 다이소 제품 검색 API를 호출합니다.',
69
- '사용법: daiso products <query> [--page N] [--pageSize N]',
70
+ '사용법: daiso products <query> [--page N] [--pageSize N] [--json]',
70
71
  '필수: <query>',
71
- '옵션: --page, --pageSize',
72
+ '옵션: --page, --pageSize, --json',
72
73
  '예시: daiso products 수납박스 --page 1 --pageSize 30',
73
74
  ],
74
75
  product: [
75
76
  '명령: product',
76
77
  '설명: 다이소 제품 상세 API를 호출합니다.',
77
- '사용법: daiso product <productId>',
78
+ '사용법: daiso product <productId> [--json]',
78
79
  '필수: <productId>',
80
+ '옵션: --json',
79
81
  '예시: daiso product 1034604',
80
82
  ],
81
83
  stores: [
82
84
  '명령: stores',
83
85
  '설명: 다이소 매장 검색 API를 호출합니다.',
84
- '사용법: daiso stores [keyword] [--sido 값] [--gugun 값] [--dong 값] [--limit N]',
86
+ '사용법: daiso stores [keyword] [--sido 값] [--gugun 값] [--dong 값] [--limit N] [--json]',
85
87
  '필수: keyword 또는 --sido',
86
- '옵션: --keyword, --sido, --gugun, --dong, --limit',
88
+ '옵션: --keyword, --sido, --gugun, --dong, --limit, --json',
87
89
  '예시: daiso stores 강남역',
88
90
  '예시: daiso stores --sido 서울 --gugun 강남구',
89
91
  ],
90
92
  inventory: [
91
93
  '명령: inventory',
92
94
  '설명: 다이소 재고 API를 호출합니다.',
93
- '사용법: daiso inventory <productId> [--keyword 값] [--lat 값] [--lng 값] [--page N] [--pageSize N]',
95
+ '사용법: daiso inventory <productId> [--keyword 값] [--lat 값] [--lng 값] [--page N] [--pageSize N] [--json]',
94
96
  '필수: <productId>',
95
- '옵션: --keyword, --lat, --lng, --page, --pageSize',
97
+ '옵션: --keyword, --lat, --lng, --page, --pageSize, --json',
96
98
  '예시: daiso inventory 1034604 --keyword 강남역',
97
99
  ],
98
100
  };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * CLI 응답 렌더러
3
+ */
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null;
6
+ }
7
+ function toText(value) {
8
+ if (value === null || value === undefined) {
9
+ return '-';
10
+ }
11
+ if (typeof value === 'string') {
12
+ return value;
13
+ }
14
+ if (typeof value === 'number' || typeof value === 'boolean') {
15
+ return String(value);
16
+ }
17
+ return JSON.stringify(value);
18
+ }
19
+ function formatMeta(meta) {
20
+ if (!isRecord(meta)) {
21
+ return [];
22
+ }
23
+ const pairs = Object.entries(meta)
24
+ .filter(([, value]) => typeof value === 'string' || typeof value === 'number')
25
+ .map(([key, value]) => `${key}=${value}`);
26
+ if (pairs.length === 0) {
27
+ return [];
28
+ }
29
+ return [`메타: ${pairs.join(', ')}`];
30
+ }
31
+ function detectName(item) {
32
+ const keys = ['name', 'productName', 'storeName', 'theaterName', 'movieName'];
33
+ for (const key of keys) {
34
+ if (typeof item[key] === 'string' && item[key].trim().length > 0) {
35
+ return item[key];
36
+ }
37
+ }
38
+ return '이름 없음';
39
+ }
40
+ function formatCollection(title, items) {
41
+ if (!Array.isArray(items)) {
42
+ return [];
43
+ }
44
+ const lines = [`${title}: ${items.length}건`];
45
+ const preview = items.slice(0, 5);
46
+ for (const entry of preview) {
47
+ if (!isRecord(entry)) {
48
+ lines.push(`- ${toText(entry)}`);
49
+ continue;
50
+ }
51
+ const name = detectName(entry);
52
+ const id = entry.id ?? entry.productId ?? entry.PD_NO ?? entry.storeCode ?? entry.theaterId ?? entry.movieId;
53
+ const price = entry.price ?? entry.prc ?? entry.PD_PRC;
54
+ let detail = `- ${name}`;
55
+ if (id !== undefined) {
56
+ detail += ` [${toText(id)}]`;
57
+ }
58
+ if (price !== undefined) {
59
+ detail += ` / ${toText(price)}원`;
60
+ }
61
+ lines.push(detail);
62
+ }
63
+ if (items.length > preview.length) {
64
+ lines.push(`...외 ${items.length - preview.length}건`);
65
+ }
66
+ return lines;
67
+ }
68
+ function formatProductDetail(data) {
69
+ const id = data.id ?? data.productId ?? '-';
70
+ const name = data.name ?? data.productName ?? '-';
71
+ const price = data.price ?? '-';
72
+ const soldOut = data.soldOut;
73
+ const lines = [`제품 상세: ${toText(name)} (${toText(id)})`, `가격: ${toText(price)}원`];
74
+ if (typeof soldOut === 'boolean') {
75
+ lines.push(`품절 여부: ${soldOut ? '품절' : '재고 있음'}`);
76
+ }
77
+ return lines;
78
+ }
79
+ function formatInventory(data) {
80
+ const lines = [];
81
+ const onlineStock = data.onlineStock;
82
+ if (onlineStock !== undefined) {
83
+ lines.push(`온라인 재고: ${toText(onlineStock)}`);
84
+ }
85
+ const storeInventory = data.storeInventory;
86
+ if (isRecord(storeInventory)) {
87
+ const total = storeInventory.totalStores;
88
+ const inStock = storeInventory.inStockCount;
89
+ lines.push(`매장 재고: 재고보유 ${toText(inStock)} / 전체 ${toText(total)}`);
90
+ lines.push(...formatCollection('매장 목록', storeInventory.stores));
91
+ }
92
+ return lines;
93
+ }
94
+ export function renderApiEnvelope(command, url, payload) {
95
+ if (!isRecord(payload)) {
96
+ return `요청 성공: ${url.pathname}\n${toText(payload)}\n\n원본 JSON은 --json 옵션으로 확인하세요.`;
97
+ }
98
+ const envelope = payload;
99
+ if (envelope.success !== true) {
100
+ return JSON.stringify(payload, null, 2);
101
+ }
102
+ const lines = [`요청 성공: ${url.pathname}`];
103
+ lines.push(...formatMeta(envelope.meta));
104
+ if (!isRecord(envelope.data)) {
105
+ lines.push(`응답 데이터: ${toText(envelope.data)}`);
106
+ lines.push('', '원본 JSON은 --json 옵션으로 확인하세요.');
107
+ return lines.join('\n');
108
+ }
109
+ const data = envelope.data;
110
+ if (command === 'product') {
111
+ lines.push(...formatProductDetail(data));
112
+ }
113
+ else if (command === 'inventory') {
114
+ lines.push(...formatInventory(data));
115
+ }
116
+ else {
117
+ lines.push(...formatCollection('제품 목록', data.products));
118
+ lines.push(...formatCollection('매장 목록', data.stores));
119
+ lines.push(...formatCollection('극장 목록', data.theaters));
120
+ lines.push(...formatCollection('영화 목록', data.movies));
121
+ lines.push(...formatCollection('시간표', data.timetable));
122
+ lines.push(...formatCollection('좌석 목록', data.seats));
123
+ lines.push(...formatCollection('상영 목록', data.showtimes));
124
+ if (lines.length <= 2) {
125
+ const summaryEntries = Object.entries(data)
126
+ .filter(([, value]) => typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
127
+ .slice(0, 8);
128
+ for (const [key, value] of summaryEntries) {
129
+ lines.push(`${key}: ${toText(value)}`);
130
+ }
131
+ }
132
+ }
133
+ lines.push('', '원본 JSON은 --json 옵션으로 확인하세요.');
134
+ return lines.join('\n');
135
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "daiso",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Daiso MCP Server for mobile access via Cloudflare Workers",
5
5
  "main": "dist/index.js",
6
6
  "bin": {