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 +4 -0
- package/dist/cli.js +48 -40
- package/dist/cliHelp.js +135 -0
- package/dist/cliRenderer.js +135 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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');
|
package/dist/cliHelp.js
ADDED
|
@@ -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
|
+
}
|