sh-ui-cli 0.94.0 → 0.95.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/data/changelog/versions.json +12 -0
- package/package.json +1 -1
- package/src/mcp.mjs +78 -2
|
@@ -2,6 +2,18 @@
|
|
|
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.95.0",
|
|
7
|
+
"date": "2026-05-15",
|
|
8
|
+
"title": "MCP describe_template 옵션 누락 fix + staleness 경고 — 외부 AI 가 stale MCP 알아채게",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**`sh_ui_describe_template` 가 tauri/i18n/locales/observability 받음** — describeTemplate 자체는 옵션을 처리하지만 MCP 핸들러의 inputSchema 에 누락되어 외부 AI 가 옵션을 명시해도 무시됐다. preview 가 항상 기본 파일만 나열되던 회귀 (#3) 해결. create_project ↔ describe_template 파일 plan 1:1 일치 보장.",
|
|
12
|
+
"**MCP staleness 경고 자동 prepend** — 서버 start 시 https://registry.npmjs.org/sh-ui-cli/latest 비동기 조회 (3s timeout). 현재 버전보다 latest 가 높으면 모든 tool 응답 상단에 `⚠ sh-ui MCP X.Y.Z (latest: …)` 한 줄 prepend. 외부 AI 가 stale MCP 를 정상 출력으로 오해하던 회귀 (#8) 차단. 오프라인 / DNS 실패는 조용히 skip — best-effort.",
|
|
13
|
+
"**`SH_UI_SKIP_STALENESS_CHECK=1` 옵트아웃** — CI / 오프라인 / 사용자가 경고 끄고 싶을 때 env var 한 줄. test runner 가 외부 fetch 없이 결정적으로 돌게 함."
|
|
14
|
+
],
|
|
15
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.95.0"
|
|
16
|
+
},
|
|
5
17
|
{
|
|
6
18
|
"version": "0.94.0",
|
|
7
19
|
"date": "2026-05-15",
|
package/package.json
CHANGED
package/src/mcp.mjs
CHANGED
|
@@ -113,11 +113,66 @@ async function captureConsole(fn) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
function textResult(text) {
|
|
116
|
-
return { content: [{ type: "text", text }] };
|
|
116
|
+
return { content: withStaleness([{ type: "text", text }]) };
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function jsonResult(data) {
|
|
120
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
120
|
+
return { content: withStaleness([{ type: "text", text: JSON.stringify(data, null, 2) }]) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Staleness 경고 — npm registry 에서 latest sh-ui-cli 버전을 조회해 현재와 다르면
|
|
125
|
+
* 모든 tool 응답 상단에 한 줄 경고 prepend (v0.95.0+).
|
|
126
|
+
*
|
|
127
|
+
* 왜 필요: npx 가 sh-ui-cli 를 한 번 fetch 한 뒤 ~/.npm/_npx 캐시에 박아두면, 사용자
|
|
128
|
+
* `~/.claude.json` 에 `sh-ui-cli` (버전 비고정) 로 등록해도 MCP 가 오래된 버전으로
|
|
129
|
+
* 계속 실행된다. AI 에이전트는 "응답이 정상 응답" 으로 받아들이므로 stale 임을 의심
|
|
130
|
+
* 못 함 — 사용자 (ai-org 케이스) 가 직접 발견할 때까지 잘못된 plan 으로 작업.
|
|
131
|
+
*
|
|
132
|
+
* 모듈 변수 — startMcpServer 가 fire-and-forget 으로 채움. 첫 응답 직전에 미완료
|
|
133
|
+
* 면 경고 없이 진행 (best-effort).
|
|
134
|
+
*/
|
|
135
|
+
let STALE_WARNING = "";
|
|
136
|
+
|
|
137
|
+
function withStaleness(content) {
|
|
138
|
+
if (!STALE_WARNING) return content;
|
|
139
|
+
return [{ type: "text", text: STALE_WARNING }, ...content];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 비동기로 npm registry 조회 → 더 높은 버전이 있으면 STALE_WARNING 셋업.
|
|
144
|
+
* 3초 timeout · 모든 실패 (오프라인 / DNS / 차단) 는 조용히 무시.
|
|
145
|
+
*/
|
|
146
|
+
async function checkStaleness(currentVersion, cliName) {
|
|
147
|
+
try {
|
|
148
|
+
const ctrl = new AbortController();
|
|
149
|
+
const timer = setTimeout(() => ctrl.abort(), 3000);
|
|
150
|
+
const res = await fetch(`https://registry.npmjs.org/${cliName}/latest`, {
|
|
151
|
+
signal: ctrl.signal,
|
|
152
|
+
});
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
if (!res.ok) return;
|
|
155
|
+
const data = await res.json();
|
|
156
|
+
const latest = typeof data.version === "string" ? data.version : null;
|
|
157
|
+
if (!latest || latest === currentVersion) return;
|
|
158
|
+
if (!isSemverGreater(latest, currentVersion)) return;
|
|
159
|
+
STALE_WARNING =
|
|
160
|
+
`⚠ sh-ui MCP ${currentVersion} (latest: ${latest}) — stale. ` +
|
|
161
|
+
`최신 옵션·버그픽스를 받으려면: \n` +
|
|
162
|
+
` • ~/.claude.json (또는 다른 MCP 설정) 의 args 를 ["sh-ui-cli@latest", "mcp"] 로 변경 후 재시작\n` +
|
|
163
|
+
` • npx 캐시가 박혀 있으면 \`npx clear-npx-cache\` 한 번 실행`;
|
|
164
|
+
} catch {
|
|
165
|
+
// best-effort.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isSemverGreater(a, b) {
|
|
170
|
+
const parse = (s) => s.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
171
|
+
const [aMaj, aMin, aPatch] = parse(a);
|
|
172
|
+
const [bMaj, bMin, bPatch] = parse(b);
|
|
173
|
+
if (aMaj !== bMaj) return aMaj > bMaj;
|
|
174
|
+
if (aMin !== bMin) return aMin > bMin;
|
|
175
|
+
return aPatch > bPatch;
|
|
121
176
|
}
|
|
122
177
|
|
|
123
178
|
async function loadRegistry(platform) {
|
|
@@ -346,6 +401,13 @@ return (
|
|
|
346
401
|
|
|
347
402
|
export async function startMcpServer() {
|
|
348
403
|
const { version, name: cliName } = await readPackageMeta();
|
|
404
|
+
|
|
405
|
+
// fire-and-forget — server 가 첫 tool 요청 받기 전에 끝나면 경고 prepend, 아니면 다음 요청부터.
|
|
406
|
+
// env SH_UI_SKIP_STALENESS_CHECK=1 로 disable 가능 (CI / 오프라인 환경).
|
|
407
|
+
if (!process.env.SH_UI_SKIP_STALENESS_CHECK) {
|
|
408
|
+
void checkStaleness(version, cliName);
|
|
409
|
+
}
|
|
410
|
+
|
|
349
411
|
const server = new McpServer(
|
|
350
412
|
{ name: "sh-ui", version },
|
|
351
413
|
{
|
|
@@ -942,6 +1004,16 @@ export async function startMcpServer() {
|
|
|
942
1004
|
.describe("CSS 프레임워크. 기본 plain. css-modules 면 page.module.css 등 추가"),
|
|
943
1005
|
appName: z.string().optional()
|
|
944
1006
|
.describe("monorepo 첫 앱 이름. 기본 web"),
|
|
1007
|
+
// vite 전용 — sh_ui_create_project 의 동일 옵션과 1:1 대응. describe ↔ create 가 같은
|
|
1008
|
+
// file-plan 을 보장하려면 여기서 받아 describeTemplate 에 전달해야 한다 (v0.95.0+).
|
|
1009
|
+
tauri: z.boolean().optional()
|
|
1010
|
+
.describe("Tauri 2.x 데스크탑 셸 emit (platform=vite 전용). 기본 false"),
|
|
1011
|
+
i18n: z.enum(I18N_LIBRARIES).optional()
|
|
1012
|
+
.describe(`i18n 라이브러리 (platform=vite 전용). 옵션: ${I18N_LIBRARIES.join(', ')}. 기본 none`),
|
|
1013
|
+
locales: z.string().optional()
|
|
1014
|
+
.describe(`i18n 활성화 시 locale 코드 (comma-separated, 예: "ko,en"). 기본 "${I18N_DEFAULT_LOCALES}"`),
|
|
1015
|
+
observability: z.enum(OBSERVABILITY_PROVIDERS).optional()
|
|
1016
|
+
.describe(`observability 백엔드 (platform=vite 전용). 옵션: ${OBSERVABILITY_PROVIDERS.join(', ')}. 기본 none`),
|
|
945
1017
|
},
|
|
946
1018
|
},
|
|
947
1019
|
async (input) => {
|
|
@@ -953,6 +1025,10 @@ export async function startMcpServer() {
|
|
|
953
1025
|
plugins: input.plugins,
|
|
954
1026
|
cssFramework: input.cssFramework,
|
|
955
1027
|
appName: input.appName,
|
|
1028
|
+
tauri: input.tauri,
|
|
1029
|
+
i18n: input.i18n,
|
|
1030
|
+
locales: input.locales,
|
|
1031
|
+
observability: input.observability,
|
|
956
1032
|
});
|
|
957
1033
|
return jsonResult(result);
|
|
958
1034
|
} catch (e) {
|