sh-ui-cli 0.44.0 → 0.45.1

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.
@@ -0,0 +1,215 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { createPortal } from "react-dom";
5
+
6
+ type ToastVariant = "default" | "success" | "danger" | "warning";
7
+
8
+ interface ToastItem {
9
+ id: string;
10
+ title?: React.ReactNode;
11
+ description?: React.ReactNode;
12
+ variant: ToastVariant;
13
+ duration: number;
14
+ action?: React.ReactNode;
15
+ }
16
+
17
+ type ToastInput = Omit<ToastItem, "id" | "variant" | "duration"> & {
18
+ variant?: ToastVariant;
19
+ duration?: number;
20
+ };
21
+
22
+ type Listener = () => void;
23
+ let toasts: ToastItem[] = [];
24
+ const listeners = new Set<Listener>();
25
+ const notify = () => listeners.forEach((l) => l());
26
+ let counter = 0;
27
+ const genId = () => `sh-toast-${++counter}`;
28
+
29
+ function addToast(input: ToastInput): string {
30
+ const id = genId();
31
+ toasts = [...toasts, {
32
+ id, variant: input.variant ?? "default", duration: input.duration ?? 4000,
33
+ title: input.title, description: input.description, action: input.action,
34
+ }];
35
+ notify();
36
+ return id;
37
+ }
38
+ function removeToast(id: string) { toasts = toasts.filter((t) => t.id !== id); notify(); }
39
+
40
+ function useToastStore() {
41
+ return React.useSyncExternalStore(
42
+ (cb) => { listeners.add(cb); return () => listeners.delete(cb); },
43
+ () => toasts,
44
+ () => toasts,
45
+ );
46
+ }
47
+
48
+ export function toast(input: ToastInput | string): string {
49
+ if (typeof input === "string") return addToast({ description: input });
50
+ return addToast(input);
51
+ }
52
+ toast.success = (input: ToastInput | string) =>
53
+ toast(typeof input === "string" ? { description: input, variant: "success" } : { ...input, variant: "success" });
54
+ toast.danger = (input: ToastInput | string) =>
55
+ toast(typeof input === "string" ? { description: input, variant: "danger" } : { ...input, variant: "danger" });
56
+ toast.warning = (input: ToastInput | string) =>
57
+ toast(typeof input === "string" ? { description: input, variant: "warning" } : { ...input, variant: "warning" });
58
+ toast.dismiss = removeToast;
59
+
60
+ function CheckIcon() {
61
+ return (
62
+ <svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden>
63
+ <circle cx="8" cy="8" r="7.25" stroke="currentColor" strokeWidth="1.5" />
64
+ <path d="M5 8.5 7 10.5 11 6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
65
+ </svg>
66
+ );
67
+ }
68
+ function AlertIcon() {
69
+ return (
70
+ <svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden>
71
+ <circle cx="8" cy="8" r="7.25" stroke="currentColor" strokeWidth="1.5" />
72
+ <path d="M8 5v3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
73
+ <circle cx="8" cy="11" r="0.75" fill="currentColor" />
74
+ </svg>
75
+ );
76
+ }
77
+ function XIcon() {
78
+ return (
79
+ <svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden>
80
+ <circle cx="8" cy="8" r="7.25" stroke="currentColor" strokeWidth="1.5" />
81
+ <path d="M6 6l4 4M10 6l-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
82
+ </svg>
83
+ );
84
+ }
85
+
86
+ const VARIANT_ICON: Record<ToastVariant, React.ReactNode> = {
87
+ default: null, success: <CheckIcon />, danger: <XIcon />, warning: <AlertIcon />,
88
+ };
89
+ const VARIANT_ICON_COLOR: Record<ToastVariant, string> = {
90
+ default: "",
91
+ success: "text-[var(--success,#16a34a)]",
92
+ danger: "text-danger",
93
+ warning: "text-[var(--warning,#d97706)]",
94
+ };
95
+
96
+ function ToastCard({ item, onDismiss }: { item: ToastItem; onDismiss: () => void }) {
97
+ const [exiting, setExiting] = React.useState(false);
98
+
99
+ React.useEffect(() => {
100
+ if (item.duration <= 0) return;
101
+ const timer = setTimeout(() => setExiting(true), item.duration);
102
+ return () => clearTimeout(timer);
103
+ }, [item.duration]);
104
+
105
+ const handleAnimationEnd = () => { if (exiting) onDismiss(); };
106
+ const icon = VARIANT_ICON[item.variant];
107
+
108
+ return (
109
+ <div
110
+ className="sh-ui-toast relative flex items-start gap-2.5 w-full pl-[var(--space-3)] pr-9 py-[var(--space-3)] bg-background text-foreground border border-border rounded-[var(--radius)] shadow-[0_4px_16px_rgba(0,0,0,0.12)] pointer-events-auto motion-reduce:!animate-none"
111
+ role={item.variant === "danger" ? "alert" : "status"}
112
+ aria-live={item.variant === "danger" ? "assertive" : "polite"}
113
+ data-exiting={exiting || undefined}
114
+ onAnimationEnd={handleAnimationEnd}
115
+ >
116
+ {icon && (
117
+ <span className={`shrink-0 inline-flex items-center mt-0.5 ${VARIANT_ICON_COLOR[item.variant]}`}>
118
+ {icon}
119
+ </span>
120
+ )}
121
+ <div className="flex-1 min-w-0">
122
+ {item.title && (
123
+ <p className="m-0 text-[length:var(--text-sm)] font-semibold leading-snug">{item.title}</p>
124
+ )}
125
+ {item.description && (
126
+ <p className="m-0 text-[0.8125rem] leading-snug text-foreground-muted [&:not(:first-child)]:mt-0.5">
127
+ {item.description}
128
+ </p>
129
+ )}
130
+ </div>
131
+ {item.action && (
132
+ <div className="shrink-0 inline-flex items-center ml-auto">{item.action}</div>
133
+ )}
134
+ <button
135
+ type="button"
136
+ className="absolute top-1.5 right-1.5 inline-flex items-center justify-center w-6 h-6 p-0 border-none rounded-[calc(var(--radius)-2px)] bg-transparent text-foreground-muted text-[length:var(--text-sm)] leading-none cursor-pointer transition-[background-color,color] duration-[var(--duration-fast)] hover:bg-background-muted hover:text-foreground focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 motion-reduce:transition-none"
137
+ onClick={() => setExiting(true)}
138
+ aria-label="닫기"
139
+ >
140
+ ×
141
+ </button>
142
+ </div>
143
+ );
144
+ }
145
+
146
+ export type ToastPosition =
147
+ | "top-left" | "top-right" | "top-center"
148
+ | "bottom-left" | "bottom-right" | "bottom-center";
149
+
150
+ export interface ToasterProps {
151
+ position?: ToastPosition;
152
+ container?: Element | null;
153
+ }
154
+
155
+ const POSITION_CLASSES: Record<ToastPosition, string> = {
156
+ "bottom-right": "bottom-[var(--space-4)] right-[var(--space-4)] flex-col-reverse max-sm:right-0 max-sm:left-0 max-sm:bottom-0",
157
+ "bottom-left": "bottom-[var(--space-4)] left-[var(--space-4)] flex-col-reverse max-sm:right-0 max-sm:left-0 max-sm:bottom-0",
158
+ "bottom-center": "bottom-[var(--space-4)] left-1/2 -translate-x-1/2 flex-col-reverse max-sm:right-0 max-sm:left-0 max-sm:bottom-0 max-sm:translate-x-0",
159
+ "top-right": "top-[var(--space-4)] right-[var(--space-4)] max-sm:right-0 max-sm:left-0 max-sm:top-0",
160
+ "top-left": "top-[var(--space-4)] left-[var(--space-4)] max-sm:right-0 max-sm:left-0 max-sm:top-0",
161
+ "top-center": "top-[var(--space-4)] left-1/2 -translate-x-1/2 max-sm:right-0 max-sm:left-0 max-sm:top-0 max-sm:translate-x-0",
162
+ };
163
+
164
+ export function Toaster({ position = "bottom-right", container }: ToasterProps) {
165
+ const items = useToastStore();
166
+ const [mounted, setMounted] = React.useState(false);
167
+
168
+ React.useEffect(() => setMounted(true), []);
169
+ if (!mounted || items.length === 0) return null;
170
+ const isBottom = position.startsWith("bottom");
171
+
172
+ const el = (
173
+ <div
174
+ className={`fixed z-[var(--z-toast)] flex gap-[var(--space-2)] max-w-96 w-full pointer-events-none max-sm:max-w-full max-sm:p-[var(--space-4)] flex-col ${POSITION_CLASSES[position]}`}
175
+ data-position={position}
176
+ aria-label="알림"
177
+ >
178
+ {(isBottom ? items : [...items].reverse()).map((item) => (
179
+ <ToastCard key={item.id} item={item} onDismiss={() => removeToast(item.id)} />
180
+ ))}
181
+ </div>
182
+ );
183
+
184
+ return createPortal(el, container ?? document.body);
185
+ }
186
+
187
+ if (typeof document !== "undefined" && !document.querySelector("style[data-sh-ui-toast]")) {
188
+ const style = document.createElement("style");
189
+ style.setAttribute("data-sh-ui-toast", "");
190
+ style.textContent = `
191
+ [data-position="bottom-right"] .sh-ui-toast, [data-position="top-right"] .sh-ui-toast { animation: sh-ui-toast-enter-right var(--duration-slow) cubic-bezier(0.16,1,0.3,1) forwards; }
192
+ [data-position="bottom-right"] .sh-ui-toast[data-exiting], [data-position="top-right"] .sh-ui-toast[data-exiting] { animation: sh-ui-toast-exit-right 150ms ease-in forwards; }
193
+ [data-position="bottom-left"] .sh-ui-toast, [data-position="top-left"] .sh-ui-toast { animation: sh-ui-toast-enter-left var(--duration-slow) cubic-bezier(0.16,1,0.3,1) forwards; }
194
+ [data-position="bottom-left"] .sh-ui-toast[data-exiting], [data-position="top-left"] .sh-ui-toast[data-exiting] { animation: sh-ui-toast-exit-left 150ms ease-in forwards; }
195
+ [data-position="bottom-center"] .sh-ui-toast { animation: sh-ui-toast-enter-bottom var(--duration-slow) cubic-bezier(0.16,1,0.3,1) forwards; }
196
+ [data-position="bottom-center"] .sh-ui-toast[data-exiting] { animation: sh-ui-toast-exit-bottom 150ms ease-in forwards; }
197
+ [data-position="top-center"] .sh-ui-toast { animation: sh-ui-toast-enter-top var(--duration-slow) cubic-bezier(0.16,1,0.3,1) forwards; }
198
+ [data-position="top-center"] .sh-ui-toast[data-exiting] { animation: sh-ui-toast-exit-top 150ms ease-in forwards; }
199
+ @keyframes sh-ui-toast-enter-right { from { opacity:0; transform: translateX(100%) } to { opacity:1; transform: translateX(0) } }
200
+ @keyframes sh-ui-toast-exit-right { from { opacity:1; transform: translateX(0) } to { opacity:0; transform: translateX(100%) } }
201
+ @keyframes sh-ui-toast-enter-left { from { opacity:0; transform: translateX(-100%) } to { opacity:1; transform: translateX(0) } }
202
+ @keyframes sh-ui-toast-exit-left { from { opacity:1; transform: translateX(0) } to { opacity:0; transform: translateX(-100%) } }
203
+ @keyframes sh-ui-toast-enter-bottom { from { opacity:0; transform: translateY(100%) } to { opacity:1; transform: translateY(0) } }
204
+ @keyframes sh-ui-toast-exit-bottom { from { opacity:1; transform: translateY(0) } to { opacity:0; transform: translateY(100%) } }
205
+ @keyframes sh-ui-toast-enter-top { from { opacity:0; transform: translateY(-100%) } to { opacity:1; transform: translateY(0) } }
206
+ @keyframes sh-ui-toast-exit-top { from { opacity:1; transform: translateY(0) } to { opacity:0; transform: translateY(-100%) } }
207
+ @media (max-width: 40rem) {
208
+ [data-position] .sh-ui-toast { animation-name: sh-ui-toast-enter-bottom !important }
209
+ [data-position^="top-"] .sh-ui-toast { animation-name: sh-ui-toast-enter-top !important }
210
+ [data-position] .sh-ui-toast[data-exiting] { animation-name: sh-ui-toast-exit-bottom !important }
211
+ [data-position^="top-"] .sh-ui-toast[data-exiting] { animation-name: sh-ui-toast-exit-top !important }
212
+ }
213
+ `;
214
+ document.head.appendChild(style);
215
+ }
@@ -130,11 +130,24 @@
130
130
  "files": [
131
131
  {
132
132
  "src": "components/file-upload/index.tsx",
133
- "dest": "{components}/file-upload/index.tsx"
133
+ "dest": "{components}/file-upload/index.tsx",
134
+ "frameworks": [
135
+ "plain"
136
+ ]
134
137
  },
135
138
  {
136
139
  "src": "components/file-upload/styles.css",
137
- "dest": "{components}/file-upload/styles.css"
140
+ "dest": "{components}/file-upload/styles.css",
141
+ "frameworks": [
142
+ "plain"
143
+ ]
144
+ },
145
+ {
146
+ "src": "components/file-upload/index.tailwind.tsx",
147
+ "dest": "{components}/file-upload/index.tsx",
148
+ "frameworks": [
149
+ "tailwind"
150
+ ]
138
151
  }
139
152
  ],
140
153
  "dependencies": [],
@@ -146,15 +159,24 @@
146
159
  "files": [
147
160
  {
148
161
  "src": "components/form/index.tsx",
149
- "dest": "{components}/form/index.tsx"
162
+ "dest": "{components}/form/index.tsx",
163
+ "frameworks": [
164
+ "plain"
165
+ ]
150
166
  },
151
167
  {
152
168
  "src": "components/form/form.tsx",
153
- "dest": "{components}/form/form.tsx"
169
+ "dest": "{components}/form/form.tsx",
170
+ "frameworks": [
171
+ "plain"
172
+ ]
154
173
  },
155
174
  {
156
175
  "src": "components/form/field.tsx",
157
- "dest": "{components}/form/field.tsx"
176
+ "dest": "{components}/form/field.tsx",
177
+ "frameworks": [
178
+ "plain"
179
+ ]
158
180
  },
159
181
  {
160
182
  "src": "components/form/steps.tsx",
@@ -190,7 +212,31 @@
190
212
  },
191
213
  {
192
214
  "src": "components/form/styles.css",
193
- "dest": "{components}/form/styles.css"
215
+ "dest": "{components}/form/styles.css",
216
+ "frameworks": [
217
+ "plain"
218
+ ]
219
+ },
220
+ {
221
+ "src": "components/form/index.tailwind.tsx",
222
+ "dest": "{components}/form/index.tsx",
223
+ "frameworks": [
224
+ "tailwind"
225
+ ]
226
+ },
227
+ {
228
+ "src": "components/form/form.tailwind.tsx",
229
+ "dest": "{components}/form/form.tsx",
230
+ "frameworks": [
231
+ "tailwind"
232
+ ]
233
+ },
234
+ {
235
+ "src": "components/form/field.tailwind.tsx",
236
+ "dest": "{components}/form/field.tsx",
237
+ "frameworks": [
238
+ "tailwind"
239
+ ]
194
240
  }
195
241
  ],
196
242
  "dependencies": [],
@@ -298,11 +344,24 @@
298
344
  "files": [
299
345
  {
300
346
  "src": "components/code-editor/index.tsx",
301
- "dest": "{components}/code-editor/index.tsx"
347
+ "dest": "{components}/code-editor/index.tsx",
348
+ "frameworks": [
349
+ "plain"
350
+ ]
302
351
  },
303
352
  {
304
353
  "src": "components/code-editor/styles.css",
305
- "dest": "{components}/code-editor/styles.css"
354
+ "dest": "{components}/code-editor/styles.css",
355
+ "frameworks": [
356
+ "plain"
357
+ ]
358
+ },
359
+ {
360
+ "src": "components/code-editor/index.tailwind.tsx",
361
+ "dest": "{components}/code-editor/index.tsx",
362
+ "frameworks": [
363
+ "tailwind"
364
+ ]
306
365
  }
307
366
  ],
308
367
  "dependencies": [
@@ -323,11 +382,24 @@
323
382
  "files": [
324
383
  {
325
384
  "src": "components/markdown-editor/index.tsx",
326
- "dest": "{components}/markdown-editor/index.tsx"
385
+ "dest": "{components}/markdown-editor/index.tsx",
386
+ "frameworks": [
387
+ "plain"
388
+ ]
327
389
  },
328
390
  {
329
391
  "src": "components/markdown-editor/styles.css",
330
- "dest": "{components}/markdown-editor/styles.css"
392
+ "dest": "{components}/markdown-editor/styles.css",
393
+ "frameworks": [
394
+ "plain"
395
+ ]
396
+ },
397
+ {
398
+ "src": "components/markdown-editor/index.tailwind.tsx",
399
+ "dest": "{components}/markdown-editor/index.tsx",
400
+ "frameworks": [
401
+ "tailwind"
402
+ ]
331
403
  }
332
404
  ],
333
405
  "dependencies": [
@@ -344,11 +416,24 @@
344
416
  "files": [
345
417
  {
346
418
  "src": "components/rich-text-editor/index.tsx",
347
- "dest": "{components}/rich-text-editor/index.tsx"
419
+ "dest": "{components}/rich-text-editor/index.tsx",
420
+ "frameworks": [
421
+ "plain"
422
+ ]
348
423
  },
349
424
  {
350
425
  "src": "components/rich-text-editor/styles.css",
351
- "dest": "{components}/rich-text-editor/styles.css"
426
+ "dest": "{components}/rich-text-editor/styles.css",
427
+ "frameworks": [
428
+ "plain"
429
+ ]
430
+ },
431
+ {
432
+ "src": "components/rich-text-editor/index.tailwind.tsx",
433
+ "dest": "{components}/rich-text-editor/index.tsx",
434
+ "frameworks": [
435
+ "tailwind"
436
+ ]
352
437
  }
353
438
  ],
354
439
  "dependencies": [
@@ -398,11 +483,24 @@
398
483
  "files": [
399
484
  {
400
485
  "src": "components/sidebar/index.tsx",
401
- "dest": "{components}/sidebar/index.tsx"
486
+ "dest": "{components}/sidebar/index.tsx",
487
+ "frameworks": [
488
+ "plain"
489
+ ]
402
490
  },
403
491
  {
404
492
  "src": "components/sidebar/styles.css",
405
- "dest": "{components}/sidebar/styles.css"
493
+ "dest": "{components}/sidebar/styles.css",
494
+ "frameworks": [
495
+ "plain"
496
+ ]
497
+ },
498
+ {
499
+ "src": "components/sidebar/index.tailwind.tsx",
500
+ "dest": "{components}/sidebar/index.tsx",
501
+ "frameworks": [
502
+ "tailwind"
503
+ ]
406
504
  }
407
505
  ],
408
506
  "dependencies": [
@@ -418,11 +516,24 @@
418
516
  "files": [
419
517
  {
420
518
  "src": "components/header/index.tsx",
421
- "dest": "{components}/header/index.tsx"
519
+ "dest": "{components}/header/index.tsx",
520
+ "frameworks": [
521
+ "plain"
522
+ ]
422
523
  },
423
524
  {
424
525
  "src": "components/header/styles.css",
425
- "dest": "{components}/header/styles.css"
526
+ "dest": "{components}/header/styles.css",
527
+ "frameworks": [
528
+ "plain"
529
+ ]
530
+ },
531
+ {
532
+ "src": "components/header/index.tailwind.tsx",
533
+ "dest": "{components}/header/index.tsx",
534
+ "frameworks": [
535
+ "tailwind"
536
+ ]
426
537
  }
427
538
  ],
428
539
  "dependencies": [],
@@ -559,11 +670,24 @@
559
670
  "files": [
560
671
  {
561
672
  "src": "components/color-picker/index.tsx",
562
- "dest": "{components}/color-picker/index.tsx"
673
+ "dest": "{components}/color-picker/index.tsx",
674
+ "frameworks": [
675
+ "plain"
676
+ ]
563
677
  },
564
678
  {
565
679
  "src": "components/color-picker/styles.css",
566
- "dest": "{components}/color-picker/styles.css"
680
+ "dest": "{components}/color-picker/styles.css",
681
+ "frameworks": [
682
+ "plain"
683
+ ]
684
+ },
685
+ {
686
+ "src": "components/color-picker/index.tailwind.tsx",
687
+ "dest": "{components}/color-picker/index.tsx",
688
+ "frameworks": [
689
+ "tailwind"
690
+ ]
567
691
  }
568
692
  ],
569
693
  "dependencies": [],
@@ -1256,11 +1380,24 @@
1256
1380
  "files": [
1257
1381
  {
1258
1382
  "src": "components/toast/index.tsx",
1259
- "dest": "{components}/toast/index.tsx"
1383
+ "dest": "{components}/toast/index.tsx",
1384
+ "frameworks": [
1385
+ "plain"
1386
+ ]
1260
1387
  },
1261
1388
  {
1262
1389
  "src": "components/toast/styles.css",
1263
- "dest": "{components}/toast/styles.css"
1390
+ "dest": "{components}/toast/styles.css",
1391
+ "frameworks": [
1392
+ "plain"
1393
+ ]
1394
+ },
1395
+ {
1396
+ "src": "components/toast/index.tailwind.tsx",
1397
+ "dest": "{components}/toast/index.tsx",
1398
+ "frameworks": [
1399
+ "tailwind"
1400
+ ]
1264
1401
  }
1265
1402
  ],
1266
1403
  "dependencies": [],
@@ -1272,11 +1409,24 @@
1272
1409
  "files": [
1273
1410
  {
1274
1411
  "src": "components/calendar/index.tsx",
1275
- "dest": "{components}/calendar/index.tsx"
1412
+ "dest": "{components}/calendar/index.tsx",
1413
+ "frameworks": [
1414
+ "plain"
1415
+ ]
1276
1416
  },
1277
1417
  {
1278
1418
  "src": "components/calendar/styles.css",
1279
- "dest": "{components}/calendar/styles.css"
1419
+ "dest": "{components}/calendar/styles.css",
1420
+ "frameworks": [
1421
+ "plain"
1422
+ ]
1423
+ },
1424
+ {
1425
+ "src": "components/calendar/index.tailwind.tsx",
1426
+ "dest": "{components}/calendar/index.tsx",
1427
+ "frameworks": [
1428
+ "tailwind"
1429
+ ]
1280
1430
  }
1281
1431
  ],
1282
1432
  "dependencies": [],
@@ -1383,11 +1533,24 @@
1383
1533
  "files": [
1384
1534
  {
1385
1535
  "src": "components/carousel/index.tsx",
1386
- "dest": "{components}/carousel/index.tsx"
1536
+ "dest": "{components}/carousel/index.tsx",
1537
+ "frameworks": [
1538
+ "plain"
1539
+ ]
1387
1540
  },
1388
1541
  {
1389
1542
  "src": "components/carousel/styles.css",
1390
- "dest": "{components}/carousel/styles.css"
1543
+ "dest": "{components}/carousel/styles.css",
1544
+ "frameworks": [
1545
+ "plain"
1546
+ ]
1547
+ },
1548
+ {
1549
+ "src": "components/carousel/index.tailwind.tsx",
1550
+ "dest": "{components}/carousel/index.tsx",
1551
+ "frameworks": [
1552
+ "tailwind"
1553
+ ]
1391
1554
  }
1392
1555
  ],
1393
1556
  "dependencies": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.44.0",
3
+ "version": "0.45.1",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/mcp.mjs CHANGED
@@ -150,9 +150,21 @@ const SERVER_INSTRUCTIONS = `sh-ui — Base UI 위에 빌드된 React/Flutter
150
150
  - \`sh_ui_get_changelog\` — 최근 변경 내역
151
151
  `;
152
152
 
153
+ /**
154
+ * 하드코딩 제거 — packages/cli/package.json 의 version 을 시작 시점에 읽어 그대로 사용.
155
+ * mcp.mjs 가 src/ 에 있고 package.json 은 그 상위 (../package.json) 라 상대 URL 로 해석.
156
+ * 출고 모드 (data/ 번들) 에서도 동일 경로 (src/mcp.mjs ↔ package.json) 라 그대로 동작.
157
+ */
158
+ async function readPackageVersion() {
159
+ const pkgUrl = new URL("../package.json", import.meta.url);
160
+ const pkg = JSON.parse(await readFile(pkgUrl, "utf8"));
161
+ return pkg.version;
162
+ }
163
+
153
164
  export async function startMcpServer() {
165
+ const version = await readPackageVersion();
154
166
  const server = new McpServer(
155
- { name: "sh-ui", version: "0.44.0" }, // sh-ui-cli 와 동기화
167
+ { name: "sh-ui", version },
156
168
  {
157
169
  capabilities: { tools: {} },
158
170
  instructions: SERVER_INSTRUCTIONS,