daiso 1.0.2 → 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
@@ -41,6 +41,7 @@ CLI (npx):
41
41
 
42
42
  ```bash
43
43
  npx daiso help
44
+ npx daiso help products
44
45
  npx daiso url
45
46
  npx daiso health
46
47
  npx daiso claude
@@ -51,6 +52,9 @@ npx daiso product 1034604
51
52
  npx daiso stores 강남역
52
53
  npx daiso inventory 1034604 --keyword 강남역
53
54
  npx daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056
55
+
56
+ # 원본 JSON 필요 시
57
+ npx daiso products 수납박스 --json
54
58
  ```
55
59
 
56
60
  <br>
package/dist/cli.js CHANGED
@@ -8,6 +8,8 @@ import { existsSync, readFileSync } from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { fileURLToPath } from 'node:url';
10
10
  import { spawn } from 'node:child_process';
11
+ import { printCommandHelp, printHelp } from './cliHelp.js';
12
+ import { renderApiEnvelope } from './cliRenderer.js';
11
13
  const DEFAULT_BASE_URL = 'https://mcp.aka.page';
12
14
  const DEFAULT_MCP_URL = `${DEFAULT_BASE_URL}/mcp`;
13
15
  function loadVersion() {
@@ -20,34 +22,6 @@ function loadVersion() {
20
22
  const parsed = JSON.parse(raw);
21
23
  return parsed.version ?? '0.0.0';
22
24
  }
23
- function printHelp(writeOut) {
24
- writeOut('daiso CLI');
25
- writeOut('');
26
- writeOut('사용법:');
27
- writeOut(' daiso <command> [options]');
28
- writeOut('');
29
- writeOut('명령어:');
30
- writeOut(' help 도움말 출력');
31
- writeOut(' version CLI 버전 출력');
32
- writeOut(' url MCP 엔드포인트 URL 출력');
33
- writeOut(' health 원격 서버 상태 확인');
34
- writeOut(' claude Claude Code MCP 등록 명령 출력');
35
- writeOut(' claude --exec Claude Code MCP 등록 명령 실행');
36
- writeOut(' get 임의 API 경로 GET 호출');
37
- writeOut(' products 다이소 제품 검색');
38
- writeOut(' product 다이소 제품 상세 조회');
39
- writeOut(' stores 다이소 매장 검색');
40
- writeOut(' inventory 다이소 재고 조회');
41
- writeOut('');
42
- writeOut('예시:');
43
- writeOut(' npx daiso url');
44
- writeOut(' npx daiso health');
45
- writeOut(' npx daiso claude');
46
- writeOut(' npx daiso products 수납박스');
47
- writeOut(' npx daiso stores 강남역');
48
- writeOut(' npx daiso inventory 1034604 --keyword 강남역');
49
- writeOut(' npx daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056');
50
- }
51
25
  async function execCommand(command, args) {
52
26
  return await new Promise((resolve, reject) => {
53
27
  const child = spawn(command, args, {
@@ -116,7 +90,17 @@ function applyOptionsToQuery(url, options) {
116
90
  url.searchParams.set(key, value);
117
91
  }
118
92
  }
119
- 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) {
120
104
  try {
121
105
  const response = await fetchImpl(url.toString());
122
106
  if (!response.ok) {
@@ -128,7 +112,12 @@ async function requestAndPrintJson(fetchImpl, writeOut, writeErr, url) {
128
112
  return 1;
129
113
  }
130
114
  const payload = (await response.json());
131
- 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
+ }
132
121
  return 0;
133
122
  }
134
123
  catch (error) {
@@ -144,6 +133,10 @@ export async function runCli(argv, deps) {
144
133
  };
145
134
  const [command, ...options] = argv;
146
135
  if (!command || command === 'help' || command === '--help' || command === '-h') {
136
+ const maybeCommand = options[0];
137
+ if (maybeCommand) {
138
+ return printCommandHelp(maybeCommand, resolvedDeps.writeOut, resolvedDeps.writeErr);
139
+ }
147
140
  printHelp(resolvedDeps.writeOut);
148
141
  return 0;
149
142
  }
@@ -193,17 +186,23 @@ export async function runCli(argv, deps) {
193
186
  }
194
187
  if (command === 'get') {
195
188
  const parsed = parseCliArgs(options);
189
+ if (parsed.options.help === 'true') {
190
+ return printCommandHelp('get', resolvedDeps.writeOut, resolvedDeps.writeErr);
191
+ }
196
192
  const targetPath = parsed.positionals[0];
197
193
  if (!targetPath) {
198
194
  resolvedDeps.writeErr('get 명령은 경로가 필요합니다. 예: daiso get /api/daiso/products --q 수납박스');
199
195
  return 1;
200
196
  }
201
197
  const targetUrl = toUrl(targetPath);
202
- applyOptionsToQuery(targetUrl, parsed.options);
203
- 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');
204
200
  }
205
201
  if (command === 'products') {
206
202
  const parsed = parseCliArgs(options);
203
+ if (parsed.options.help === 'true') {
204
+ return printCommandHelp('products', resolvedDeps.writeOut, resolvedDeps.writeErr);
205
+ }
207
206
  const query = parsed.positionals[0];
208
207
  if (!query) {
209
208
  resolvedDeps.writeErr('products 명령은 검색어가 필요합니다. 예: daiso products 수납박스');
@@ -211,22 +210,28 @@ export async function runCli(argv, deps) {
211
210
  }
212
211
  const targetUrl = toUrl('/api/daiso/products');
213
212
  targetUrl.searchParams.set('q', query);
214
- applyOptionsToQuery(targetUrl, parsed.options);
215
- 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');
216
215
  }
217
216
  if (command === 'product') {
218
217
  const parsed = parseCliArgs(options);
218
+ if (parsed.options.help === 'true') {
219
+ return printCommandHelp('product', resolvedDeps.writeOut, resolvedDeps.writeErr);
220
+ }
219
221
  const productId = parsed.positionals[0];
220
222
  if (!productId) {
221
223
  resolvedDeps.writeErr('product 명령은 제품 ID가 필요합니다. 예: daiso product 1034604');
222
224
  return 1;
223
225
  }
224
226
  const targetUrl = toUrl(`/api/daiso/products/${productId}`);
225
- applyOptionsToQuery(targetUrl, parsed.options);
226
- 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');
227
229
  }
228
230
  if (command === 'stores') {
229
231
  const parsed = parseCliArgs(options);
232
+ if (parsed.options.help === 'true') {
233
+ return printCommandHelp('stores', resolvedDeps.writeOut, resolvedDeps.writeErr);
234
+ }
230
235
  const keyword = parsed.positionals[0];
231
236
  if (keyword) {
232
237
  parsed.options.keyword = keyword;
@@ -236,11 +241,14 @@ export async function runCli(argv, deps) {
236
241
  return 1;
237
242
  }
238
243
  const targetUrl = toUrl('/api/daiso/stores');
239
- applyOptionsToQuery(targetUrl, parsed.options);
240
- 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');
241
246
  }
242
247
  if (command === 'inventory') {
243
248
  const parsed = parseCliArgs(options);
249
+ if (parsed.options.help === 'true') {
250
+ return printCommandHelp('inventory', resolvedDeps.writeOut, resolvedDeps.writeErr);
251
+ }
244
252
  const productId = parsed.positionals[0];
245
253
  if (!productId) {
246
254
  resolvedDeps.writeErr('inventory 명령은 제품 ID가 필요합니다. 예: daiso inventory 1034604 --keyword 강남역');
@@ -248,8 +256,8 @@ export async function runCli(argv, deps) {
248
256
  }
249
257
  const targetUrl = toUrl('/api/daiso/inventory');
250
258
  targetUrl.searchParams.set('productId', productId);
251
- applyOptionsToQuery(targetUrl, parsed.options);
252
- 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');
253
261
  }
254
262
  resolvedDeps.writeErr(`알 수 없는 명령어: ${command}`);
255
263
  resolvedDeps.writeErr('도움말: daiso help');
@@ -0,0 +1,135 @@
1
+ /**
2
+ * CLI 도움말 정의
3
+ */
4
+ export const COMMAND_LIST = [
5
+ 'help',
6
+ 'version',
7
+ 'url',
8
+ 'health',
9
+ 'claude',
10
+ 'get',
11
+ 'products',
12
+ 'product',
13
+ 'stores',
14
+ 'inventory',
15
+ ];
16
+ const COMMAND_SUMMARY = {
17
+ help: '도움말 출력',
18
+ version: 'CLI 버전 출력',
19
+ url: 'MCP 엔드포인트 URL 출력',
20
+ health: '원격 서버 상태 확인',
21
+ claude: 'Claude Code MCP 등록 명령 출력/실행',
22
+ get: '임의 API 경로 GET 호출',
23
+ products: '다이소 제품 검색',
24
+ product: '다이소 제품 상세 조회',
25
+ stores: '다이소 매장 검색',
26
+ inventory: '다이소 재고 조회',
27
+ };
28
+ const COMMAND_DETAIL = {
29
+ help: [
30
+ '명령: help',
31
+ '설명: 전체 도움말 또는 특정 명령 상세 도움말을 출력합니다.',
32
+ '사용법: daiso help [command]',
33
+ '예시: daiso help products',
34
+ ],
35
+ version: [
36
+ '명령: version',
37
+ '설명: 설치된 daiso CLI 버전을 출력합니다.',
38
+ '사용법: daiso version',
39
+ ],
40
+ url: [
41
+ '명령: url',
42
+ '설명: MCP 엔드포인트 URL을 출력합니다.',
43
+ '사용법: daiso url',
44
+ '출력 예시: https://mcp.aka.page/mcp',
45
+ ],
46
+ health: [
47
+ '명령: health',
48
+ '설명: 서버 /health API를 호출해 상태를 확인합니다.',
49
+ '사용법: daiso health',
50
+ '출력: status, endpoint, checkedAt(JSON)',
51
+ ],
52
+ claude: [
53
+ '명령: claude',
54
+ '설명: Claude Code MCP 등록 명령을 출력하거나 실행합니다.',
55
+ '사용법: daiso claude [--exec]',
56
+ '옵션: --exec 실제 claude 명령 실행',
57
+ '예시: daiso claude --exec',
58
+ ],
59
+ get: [
60
+ '명령: get',
61
+ '설명: 임의 API 경로를 GET으로 호출합니다.',
62
+ '사용법: daiso get <path|url> [--queryKey value ...] [--json]',
63
+ '필수: <path|url>',
64
+ '옵션: --json 원본 JSON 출력',
65
+ '예시: daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056',
66
+ ],
67
+ products: [
68
+ '명령: products',
69
+ '설명: 다이소 제품 검색 API를 호출합니다.',
70
+ '사용법: daiso products <query> [--page N] [--pageSize N] [--json]',
71
+ '필수: <query>',
72
+ '옵션: --page, --pageSize, --json',
73
+ '예시: daiso products 수납박스 --page 1 --pageSize 30',
74
+ ],
75
+ product: [
76
+ '명령: product',
77
+ '설명: 다이소 제품 상세 API를 호출합니다.',
78
+ '사용법: daiso product <productId> [--json]',
79
+ '필수: <productId>',
80
+ '옵션: --json',
81
+ '예시: daiso product 1034604',
82
+ ],
83
+ stores: [
84
+ '명령: stores',
85
+ '설명: 다이소 매장 검색 API를 호출합니다.',
86
+ '사용법: daiso stores [keyword] [--sido 값] [--gugun 값] [--dong 값] [--limit N] [--json]',
87
+ '필수: keyword 또는 --sido',
88
+ '옵션: --keyword, --sido, --gugun, --dong, --limit, --json',
89
+ '예시: daiso stores 강남역',
90
+ '예시: daiso stores --sido 서울 --gugun 강남구',
91
+ ],
92
+ inventory: [
93
+ '명령: inventory',
94
+ '설명: 다이소 재고 API를 호출합니다.',
95
+ '사용법: daiso inventory <productId> [--keyword 값] [--lat 값] [--lng 값] [--page N] [--pageSize N] [--json]',
96
+ '필수: <productId>',
97
+ '옵션: --keyword, --lat, --lng, --page, --pageSize, --json',
98
+ '예시: daiso inventory 1034604 --keyword 강남역',
99
+ ],
100
+ };
101
+ export function printHelp(writeOut) {
102
+ writeOut('daiso CLI');
103
+ writeOut('');
104
+ writeOut('사용법:');
105
+ writeOut(' daiso <command> [options]');
106
+ writeOut('');
107
+ writeOut('명령어:');
108
+ for (const command of COMMAND_LIST) {
109
+ writeOut(` ${command.padEnd(20, ' ')}${COMMAND_SUMMARY[command]}`);
110
+ }
111
+ writeOut('');
112
+ writeOut('예시:');
113
+ writeOut(' npx daiso url');
114
+ writeOut(' npx daiso health');
115
+ writeOut(' npx daiso claude');
116
+ writeOut(' npx daiso products 수납박스');
117
+ writeOut(' npx daiso stores 강남역');
118
+ writeOut(' npx daiso inventory 1034604 --keyword 강남역');
119
+ writeOut(' npx daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056');
120
+ writeOut('');
121
+ writeOut('상세 도움말:');
122
+ writeOut(' npx daiso help <command>');
123
+ }
124
+ export function printCommandHelp(command, writeOut, writeErr) {
125
+ if (!Object.hasOwn(COMMAND_DETAIL, command)) {
126
+ writeErr(`도움말을 찾을 수 없는 명령어: ${command}`);
127
+ writeErr('사용 가능한 명령어: help, version, url, health, claude, get, products, product, stores, inventory');
128
+ return 1;
129
+ }
130
+ const detail = COMMAND_DETAIL[command];
131
+ for (const line of detail) {
132
+ writeOut(line);
133
+ }
134
+ return 0;
135
+ }
@@ -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.2",
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": {