sh-ui-cli 0.21.1 → 0.22.0
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 +73 -0
- package/bin/sh-ui.mjs +11 -2
- package/data/changelog/versions.json +25 -0
- package/data/registry/react/components/accordion/index.tsx +1 -1
- package/data/registry/react/components/avatar/index.tsx +1 -1
- package/data/registry/react/components/checkbox/index.tsx +2 -2
- package/data/registry/react/components/combobox/index.tsx +1 -1
- package/data/registry/react/components/context-menu/index.tsx +1 -1
- package/data/registry/react/components/date-picker/index.tsx +1 -1
- package/data/registry/react/components/dialog/index.tsx +1 -1
- package/data/registry/react/components/dropdown-menu/index.tsx +1 -1
- package/data/registry/react/components/menubar/index.tsx +1 -1
- package/data/registry/react/components/popover/index.tsx +1 -1
- package/data/registry/react/components/radio/index.tsx +2 -2
- package/data/registry/react/components/select/index.tsx +1 -1
- package/data/registry/react/components/switch/index.tsx +1 -1
- package/data/registry/react/components/tabs/index.tsx +1 -1
- package/data/registry/react/components/toggle/index.tsx +2 -2
- package/data/registry/react/components/tooltip/index.tsx +1 -1
- package/data/registry/react/peer-versions.json +1 -1
- package/data/registry/react/registry.json +16 -16
- package/package.json +1 -1
- package/src/mcp-init.mjs +141 -0
- package/src/mcp.mjs +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,79 @@ npx sh-ui list
|
|
|
51
51
|
npx sh-ui remove button
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
### mcp — AI 에게 sh-ui 를 알려주기 (v0.21.0+)
|
|
55
|
+
|
|
56
|
+
`sh-ui mcp` 는 [Model Context Protocol](https://modelcontextprotocol.io) 서버를 stdio 로 시작한다. IDE-내 AI(Claude Code, Cursor, Windsurf 등) 가 sh-ui 컴포넌트를 자동으로 검색·설치할 수 있게 7개 툴을 노출한다.
|
|
57
|
+
|
|
58
|
+
**한 번만 등록하면 끝** — 빈 폴더에서도 _"다크 모던 sh-ui 로 세팅하고 button 추가해줘"_ 만 말하면 AI 가 알아서 처리.
|
|
59
|
+
|
|
60
|
+
#### 자동 등록 (권장, v0.22.0+)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 프로젝트 루트에서 — IDE 별로 한 줄
|
|
64
|
+
npx sh-ui mcp init --client claude-code # → .mcp.json
|
|
65
|
+
npx sh-ui mcp init --client cursor # → .cursor/mcp.json
|
|
66
|
+
npx sh-ui mcp init --client claude-desktop # → 사용자 전역 (재시작 필요)
|
|
67
|
+
|
|
68
|
+
# 사용자 전역 설정에 등록하려면
|
|
69
|
+
npx sh-ui mcp init --client claude-code --scope user
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
기존 설정 파일이 있으면 다른 MCP 서버 엔트리를 보존하며 `sh-ui` 만 머지·갱신.
|
|
73
|
+
|
|
74
|
+
#### 수동 등록
|
|
75
|
+
|
|
76
|
+
**Claude Code** — `~/.claude/mcp.json` 또는 프로젝트 `.mcp.json`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"sh-ui": {
|
|
82
|
+
"command": "npx",
|
|
83
|
+
"args": ["-y", "sh-ui-cli", "mcp"]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Cursor** — `~/.cursor/mcp.json` 또는 `.cursor/mcp.json`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"mcpServers": {
|
|
94
|
+
"sh-ui": {
|
|
95
|
+
"command": "npx",
|
|
96
|
+
"args": ["-y", "sh-ui-cli", "mcp"]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"mcpServers": {
|
|
107
|
+
"sh-ui": {
|
|
108
|
+
"command": "npx",
|
|
109
|
+
"args": ["-y", "sh-ui-cli", "mcp"]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### 노출되는 툴
|
|
116
|
+
|
|
117
|
+
| 툴 | 설명 |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `sh_ui_describe_init` | platform/base/radius/mode 선택지 + 한글 설명 — 자연어 의도("다크 모던") → enum 매핑용 |
|
|
120
|
+
| `sh_ui_init` | `sh-ui.config.json` 생성 (비대화형) |
|
|
121
|
+
| `sh_ui_list_components` | 플랫폼별 전체 컴포넌트 + 요약 + deps |
|
|
122
|
+
| `sh_ui_get_component` | 단일 컴포넌트 메타·소스파일·deps |
|
|
123
|
+
| `sh_ui_add_component` | 컴포넌트 설치 (외부 패키지 자동 설치) |
|
|
124
|
+
| `sh_ui_remove_component` | 삭제 (수정 파일 보호) |
|
|
125
|
+
| `sh_ui_get_changelog` | 변경 내역 조회 |
|
|
126
|
+
|
|
54
127
|
## 지원 플랫폼
|
|
55
128
|
|
|
56
129
|
- **React (Next.js)** — `src/shared/ui/` 또는 `sh-ui.config.json` 에 지정된 경로로 복사
|
package/bin/sh-ui.mjs
CHANGED
|
@@ -14,6 +14,8 @@ const usage = `사용법:
|
|
|
14
14
|
sh-ui list 현재 설치된 컴포넌트 목록 표시
|
|
15
15
|
sh-ui remove <component...> 설치된 컴포넌트 파일 삭제
|
|
16
16
|
sh-ui mcp MCP 서버(stdio) 시작 — IDE-내 AI용
|
|
17
|
+
sh-ui mcp init --client <name> IDE MCP 설정 파일에 sh-ui 엔트리 자동 추가
|
|
18
|
+
(claude-code | cursor | claude-desktop)
|
|
17
19
|
옵션:
|
|
18
20
|
--skip-install (add) 외부 패키지 자동 설치 생략
|
|
19
21
|
--diff (add) 파일을 쓰지 않고 변경 내역만 출력
|
|
@@ -45,8 +47,15 @@ try {
|
|
|
45
47
|
break;
|
|
46
48
|
}
|
|
47
49
|
case "mcp": {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
// `sh-ui mcp init ...` → 설정 파일에 엔트리 추가
|
|
51
|
+
// `sh-ui mcp` → MCP 서버 시작
|
|
52
|
+
if (rest[0] === "init") {
|
|
53
|
+
const { mcpInit } = await import("../src/mcp-init.mjs");
|
|
54
|
+
await mcpInit({ cwd: process.cwd(), args: rest.slice(1) });
|
|
55
|
+
} else {
|
|
56
|
+
const { startMcpServer } = await import("../src/mcp.mjs");
|
|
57
|
+
await startMcpServer();
|
|
58
|
+
}
|
|
50
59
|
break;
|
|
51
60
|
}
|
|
52
61
|
case "remove":
|
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$description": "sh-ui 릴리즈 노트 단일 소스. docs(React)와 showcase(Flutter)가 함께 읽는다. 새 릴리즈마다 맨 앞에 추가.",
|
|
4
4
|
"versions": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.22.0",
|
|
7
|
+
"date": "2026-04-27",
|
|
8
|
+
"title": "sh-ui mcp init — IDE MCP 설정 자동 등록",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"sh-ui mcp init --client <claude-code|cursor|claude-desktop> 추가 — IDE 별 MCP 설정 파일을 자동으로 찾아 sh-ui 엔트리 머지",
|
|
12
|
+
"스코프 분기 — 기본 project(.mcp.json / .cursor/mcp.json), --scope user 로 전역(~/.claude/mcp.json 등) 선택 가능. claude-desktop 은 user 전용(OS 별 경로 자동 분기)",
|
|
13
|
+
"기존 JSON 보존 — 다른 MCP 서버 엔트리·기타 키를 건드리지 않고 sh-ui 만 머지·갱신",
|
|
14
|
+
"수동 JSON 편집 없이 npx sh-ui mcp init --client cursor 한 줄로 끝"
|
|
15
|
+
],
|
|
16
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.22.0"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"version": "0.21.2",
|
|
20
|
+
"date": "2026-04-27",
|
|
21
|
+
"title": "@base-ui-components/react → @base-ui/react 마이그레이션",
|
|
22
|
+
"type": "patch",
|
|
23
|
+
"highlights": [
|
|
24
|
+
"Base UI 패키지명 변경에 대응 — @base-ui-components/react (deprecated, RC만 존재) → @base-ui/react ^1.4.1 (stable). 17개 컴포넌트 + docs 듀얼 카피 + registry.json + peer-versions.json 일괄 갱신",
|
|
25
|
+
"deprecation 워닝 제거 — 신규 사용자가 sh-ui add 시 deprecated 패키지 경고 없이 stable 1.4.1 자동 설치",
|
|
26
|
+
"submodule 경로(@base-ui/react/dialog 등)는 동일하므로 사용자 코드 호환성 유지"
|
|
27
|
+
],
|
|
28
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.21.2"
|
|
29
|
+
},
|
|
5
30
|
{
|
|
6
31
|
"version": "0.21.1",
|
|
7
32
|
"date": "2026-04-27",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Accordion as BaseAccordion } from "@base-ui
|
|
2
|
+
import { Accordion as BaseAccordion } from "@base-ui/react/accordion";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
5
|
function cx(...args: (string | undefined | false)[]) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Avatar as BaseAvatar } from "@base-ui
|
|
2
|
+
import { Avatar as BaseAvatar } from "@base-ui/react/avatar";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
5
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Checkbox as BaseCheckbox } from "@base-ui
|
|
3
|
-
import { CheckboxGroup as BaseCheckboxGroup } from "@base-ui
|
|
2
|
+
import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
|
|
3
|
+
import { CheckboxGroup as BaseCheckboxGroup } from "@base-ui/react/checkbox-group";
|
|
4
4
|
import "./styles.css";
|
|
5
5
|
|
|
6
6
|
function cx(...args: (string | undefined | false)[]) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { Combobox as BaseCombobox } from "@base-ui
|
|
4
|
+
import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
|
|
5
5
|
import "./styles.css";
|
|
6
6
|
|
|
7
7
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { ContextMenu as BaseContextMenu } from "@base-ui
|
|
2
|
+
import { ContextMenu as BaseContextMenu } from "@base-ui/react/context-menu";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
5
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Dialog as BaseDialog } from "@base-ui
|
|
2
|
+
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
5
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { Menu as BaseMenu } from "@base-ui
|
|
4
|
+
import { Menu as BaseMenu } from "@base-ui/react/menu";
|
|
5
5
|
import "./styles.css";
|
|
6
6
|
|
|
7
7
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Menubar as BaseMenubar } from "@base-ui
|
|
2
|
+
import { Menubar as BaseMenubar } from "@base-ui/react/menubar";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
5
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Popover as BasePopover } from "@base-ui
|
|
2
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
5
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Radio as BaseRadio } from "@base-ui
|
|
3
|
-
import { RadioGroup as BaseRadioGroup } from "@base-ui
|
|
2
|
+
import { Radio as BaseRadio } from "@base-ui/react/radio";
|
|
3
|
+
import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
|
|
4
4
|
import "./styles.css";
|
|
5
5
|
|
|
6
6
|
function cx(...args: (string | undefined | false)[]) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { Select as BaseSelect } from "@base-ui
|
|
4
|
+
import { Select as BaseSelect } from "@base-ui/react/select";
|
|
5
5
|
import "./styles.css";
|
|
6
6
|
|
|
7
7
|
function cx(...args: (string | undefined | false)[]) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { Toggle as BaseToggle } from "@base-ui
|
|
5
|
-
import { ToggleGroup as BaseToggleGroup } from "@base-ui
|
|
4
|
+
import { Toggle as BaseToggle } from "@base-ui/react/toggle";
|
|
5
|
+
import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
|
|
6
6
|
import "./styles.css";
|
|
7
7
|
|
|
8
8
|
function cx(...args: (string | undefined | false)[]) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Tooltip as BaseTooltip } from "@base-ui
|
|
2
|
+
import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
|
|
3
3
|
import "./styles.css";
|
|
4
4
|
|
|
5
5
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$description": "registry.json 의 dependencies 에 등장하는 npm 패키지의 버전 범위. CLI 가 add 시 npm install <name>@<range> 로 호출하기 위함.",
|
|
3
3
|
"versions": {
|
|
4
|
-
"@base-ui
|
|
4
|
+
"@base-ui/react": "^1.4.1",
|
|
5
5
|
"@tanstack/react-form": "^1.29.1",
|
|
6
6
|
"lucide-react": "^1.11.0",
|
|
7
7
|
"react-hook-form": "^7.74.0",
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
}
|
|
172
172
|
],
|
|
173
173
|
"dependencies": [
|
|
174
|
-
"@base-ui
|
|
174
|
+
"@base-ui/react"
|
|
175
175
|
],
|
|
176
176
|
"registryDependencies": []
|
|
177
177
|
},
|
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
}
|
|
370
370
|
],
|
|
371
371
|
"dependencies": [
|
|
372
|
-
"@base-ui
|
|
372
|
+
"@base-ui/react"
|
|
373
373
|
],
|
|
374
374
|
"registryDependencies": []
|
|
375
375
|
},
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
}
|
|
388
388
|
],
|
|
389
389
|
"dependencies": [
|
|
390
|
-
"@base-ui
|
|
390
|
+
"@base-ui/react"
|
|
391
391
|
],
|
|
392
392
|
"registryDependencies": []
|
|
393
393
|
},
|
|
@@ -405,7 +405,7 @@
|
|
|
405
405
|
}
|
|
406
406
|
],
|
|
407
407
|
"dependencies": [
|
|
408
|
-
"@base-ui
|
|
408
|
+
"@base-ui/react"
|
|
409
409
|
],
|
|
410
410
|
"registryDependencies": []
|
|
411
411
|
},
|
|
@@ -423,7 +423,7 @@
|
|
|
423
423
|
}
|
|
424
424
|
],
|
|
425
425
|
"dependencies": [
|
|
426
|
-
"@base-ui
|
|
426
|
+
"@base-ui/react"
|
|
427
427
|
],
|
|
428
428
|
"registryDependencies": []
|
|
429
429
|
},
|
|
@@ -441,7 +441,7 @@
|
|
|
441
441
|
}
|
|
442
442
|
],
|
|
443
443
|
"dependencies": [
|
|
444
|
-
"@base-ui
|
|
444
|
+
"@base-ui/react"
|
|
445
445
|
],
|
|
446
446
|
"registryDependencies": []
|
|
447
447
|
},
|
|
@@ -459,7 +459,7 @@
|
|
|
459
459
|
}
|
|
460
460
|
],
|
|
461
461
|
"dependencies": [
|
|
462
|
-
"@base-ui
|
|
462
|
+
"@base-ui/react"
|
|
463
463
|
],
|
|
464
464
|
"registryDependencies": []
|
|
465
465
|
},
|
|
@@ -477,7 +477,7 @@
|
|
|
477
477
|
}
|
|
478
478
|
],
|
|
479
479
|
"dependencies": [
|
|
480
|
-
"@base-ui
|
|
480
|
+
"@base-ui/react"
|
|
481
481
|
],
|
|
482
482
|
"registryDependencies": [
|
|
483
483
|
"dropdown-menu"
|
|
@@ -497,7 +497,7 @@
|
|
|
497
497
|
}
|
|
498
498
|
],
|
|
499
499
|
"dependencies": [
|
|
500
|
-
"@base-ui
|
|
500
|
+
"@base-ui/react"
|
|
501
501
|
],
|
|
502
502
|
"registryDependencies": []
|
|
503
503
|
},
|
|
@@ -547,7 +547,7 @@
|
|
|
547
547
|
}
|
|
548
548
|
],
|
|
549
549
|
"dependencies": [
|
|
550
|
-
"@base-ui
|
|
550
|
+
"@base-ui/react"
|
|
551
551
|
],
|
|
552
552
|
"registryDependencies": []
|
|
553
553
|
},
|
|
@@ -641,7 +641,7 @@
|
|
|
641
641
|
}
|
|
642
642
|
],
|
|
643
643
|
"dependencies": [
|
|
644
|
-
"@base-ui
|
|
644
|
+
"@base-ui/react"
|
|
645
645
|
],
|
|
646
646
|
"registryDependencies": []
|
|
647
647
|
},
|
|
@@ -659,7 +659,7 @@
|
|
|
659
659
|
}
|
|
660
660
|
],
|
|
661
661
|
"dependencies": [
|
|
662
|
-
"@base-ui
|
|
662
|
+
"@base-ui/react"
|
|
663
663
|
],
|
|
664
664
|
"registryDependencies": []
|
|
665
665
|
},
|
|
@@ -677,7 +677,7 @@
|
|
|
677
677
|
}
|
|
678
678
|
],
|
|
679
679
|
"dependencies": [
|
|
680
|
-
"@base-ui
|
|
680
|
+
"@base-ui/react"
|
|
681
681
|
],
|
|
682
682
|
"registryDependencies": []
|
|
683
683
|
},
|
|
@@ -695,7 +695,7 @@
|
|
|
695
695
|
}
|
|
696
696
|
],
|
|
697
697
|
"dependencies": [
|
|
698
|
-
"@base-ui
|
|
698
|
+
"@base-ui/react"
|
|
699
699
|
],
|
|
700
700
|
"registryDependencies": []
|
|
701
701
|
},
|
|
@@ -745,7 +745,7 @@
|
|
|
745
745
|
}
|
|
746
746
|
],
|
|
747
747
|
"dependencies": [
|
|
748
|
-
"@base-ui
|
|
748
|
+
"@base-ui/react"
|
|
749
749
|
],
|
|
750
750
|
"registryDependencies": []
|
|
751
751
|
},
|
|
@@ -779,7 +779,7 @@
|
|
|
779
779
|
}
|
|
780
780
|
],
|
|
781
781
|
"dependencies": [
|
|
782
|
-
"@base-ui
|
|
782
|
+
"@base-ui/react"
|
|
783
783
|
],
|
|
784
784
|
"registryDependencies": []
|
|
785
785
|
},
|
package/package.json
CHANGED
package/src/mcp-init.mjs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// sh-ui mcp init — 타깃 IDE 의 MCP 설정 파일에 sh-ui 엔트리를 자동 추가.
|
|
2
|
+
//
|
|
3
|
+
// 지원 클라이언트:
|
|
4
|
+
// claude-code — project: <cwd>/.mcp.json, user: ~/.claude/mcp.json
|
|
5
|
+
// cursor — project: <cwd>/.cursor/mcp.json, user: ~/.cursor/mcp.json
|
|
6
|
+
// claude-desktop — user 만 (OS 별 경로 자동 분기)
|
|
7
|
+
//
|
|
8
|
+
// 동작: 기존 JSON 의 mcpServers.sh-ui 를 머지(있으면 덮어쓰기), 디렉토리 자동 생성.
|
|
9
|
+
|
|
10
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { dirname, resolve, relative } from "node:path";
|
|
13
|
+
import { homedir, platform as osPlatform } from "node:os";
|
|
14
|
+
|
|
15
|
+
const CLIENTS = ["claude-code", "cursor", "claude-desktop"];
|
|
16
|
+
|
|
17
|
+
const SH_UI_ENTRY = {
|
|
18
|
+
command: "npx",
|
|
19
|
+
args: ["-y", "sh-ui-cli", "mcp"],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** 클라이언트·스코프별 설정 파일 절대 경로. */
|
|
23
|
+
function resolveConfigPath(client, scope, cwd) {
|
|
24
|
+
const home = homedir();
|
|
25
|
+
if (client === "claude-code") {
|
|
26
|
+
return scope === "user"
|
|
27
|
+
? resolve(home, ".claude", "mcp.json")
|
|
28
|
+
: resolve(cwd, ".mcp.json");
|
|
29
|
+
}
|
|
30
|
+
if (client === "cursor") {
|
|
31
|
+
return scope === "user"
|
|
32
|
+
? resolve(home, ".cursor", "mcp.json")
|
|
33
|
+
: resolve(cwd, ".cursor", "mcp.json");
|
|
34
|
+
}
|
|
35
|
+
if (client === "claude-desktop") {
|
|
36
|
+
if (scope !== "user") {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"claude-desktop 은 user 스코프만 지원합니다. --scope user 또는 --scope 생략.",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const os = osPlatform();
|
|
42
|
+
if (os === "darwin") {
|
|
43
|
+
return resolve(
|
|
44
|
+
home,
|
|
45
|
+
"Library",
|
|
46
|
+
"Application Support",
|
|
47
|
+
"Claude",
|
|
48
|
+
"claude_desktop_config.json",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (os === "win32") {
|
|
52
|
+
const appData = process.env.APPDATA ?? resolve(home, "AppData", "Roaming");
|
|
53
|
+
return resolve(appData, "Claude", "claude_desktop_config.json");
|
|
54
|
+
}
|
|
55
|
+
// linux + 기타
|
|
56
|
+
return resolve(home, ".config", "Claude", "claude_desktop_config.json");
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`알 수 없는 클라이언트: ${client}. 허용: ${CLIENTS.join(", ")}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 클라이언트별 기본 스코프. claude-desktop 은 user 강제. */
|
|
62
|
+
function defaultScope(client) {
|
|
63
|
+
return client === "claude-desktop" ? "user" : "project";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** JSON 읽기 (없으면 빈 객체). 깨진 JSON 은 명시적 에러. */
|
|
67
|
+
async function readJsonOrEmpty(path) {
|
|
68
|
+
if (!existsSync(path)) return {};
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = await readFile(path, "utf8");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
throw new Error(`설정 파일 읽기 실패: ${path}\n ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
if (raw.trim() === "") return {};
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(raw);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`기존 ${path} 가 유효한 JSON 이 아닙니다. 수동으로 고치고 다시 시도하세요.\n ${err.message}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function mcpInit({ cwd, args }) {
|
|
86
|
+
const flags = parseFlags(args);
|
|
87
|
+
const client = flags.client;
|
|
88
|
+
if (!client) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`--client 가 필요합니다. 허용: ${CLIENTS.join(", ")}\n` +
|
|
91
|
+
`예: sh-ui mcp init --client claude-code`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (!CLIENTS.includes(client)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`알 수 없는 클라이언트: '${client}'. 허용: ${CLIENTS.join(", ")}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const scope = flags.scope ?? defaultScope(client);
|
|
101
|
+
if (!["user", "project"].includes(scope)) {
|
|
102
|
+
throw new Error(`--scope 는 'user' 또는 'project' 여야 합니다.`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const configPath = resolveConfigPath(client, scope, cwd);
|
|
106
|
+
const config = await readJsonOrEmpty(configPath);
|
|
107
|
+
|
|
108
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
109
|
+
config.mcpServers = {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const before = config.mcpServers["sh-ui"];
|
|
113
|
+
config.mcpServers["sh-ui"] = SH_UI_ENTRY;
|
|
114
|
+
|
|
115
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
116
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
117
|
+
|
|
118
|
+
const rel = relative(cwd, configPath);
|
|
119
|
+
const display = rel.startsWith("..") ? configPath : rel;
|
|
120
|
+
const verb = before ? "갱신" : "추가";
|
|
121
|
+
console.log(`✓ sh-ui MCP 엔트리 ${verb} → ${display}`);
|
|
122
|
+
console.log(` client: ${client} (scope: ${scope})`);
|
|
123
|
+
if (client === "claude-code" || client === "cursor") {
|
|
124
|
+
console.log(`\n다음 단계: ${client === "claude-code" ? "Claude Code" : "Cursor"} 를 재시작하면 sh-ui 툴이 활성화됩니다.`);
|
|
125
|
+
} else {
|
|
126
|
+
console.log(`\n다음 단계: Claude Desktop 을 종료 후 재시작하면 sh-ui 툴이 활성화됩니다.`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** --key=value / --key value 파싱 */
|
|
131
|
+
function parseFlags(args) {
|
|
132
|
+
const flags = {};
|
|
133
|
+
for (let i = 0; i < args.length; i++) {
|
|
134
|
+
const a = args[i];
|
|
135
|
+
if (!a.startsWith("--")) continue;
|
|
136
|
+
const eq = a.indexOf("=");
|
|
137
|
+
if (eq > -1) flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
138
|
+
else flags[a.slice(2)] = args[++i];
|
|
139
|
+
}
|
|
140
|
+
return flags;
|
|
141
|
+
}
|
package/src/mcp.mjs
CHANGED