sh-ui-cli 0.89.1 → 0.91.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.
@@ -2,6 +2,33 @@
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.91.0",
7
+ "date": "2026-05-14",
8
+ "title": "add-app 가 platform (next/vite) + tauri 인식 — vite monorepo 에 새 앱 정상 추가",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "**`sh-ui-cli add-app` + MCP `sh_ui_add_app` 가 `platform: vite | next` 인식** — 이전엔 Next.js 하드코딩이라 vite monorepo (v0.87.0+) 에 `add-app` 하면 잘못된 Next.js 앱이 들어가던 빈틈 해소. `--platform` 미지정 시 기존 `apps/*` 의 `package.json` deps 스캔해서 platform 추론 (모든 앱이 같은 플랫폼이면 그 값으로). 혼재 or 빈 apps/ 면 TTY 면 prompt, 비대화형이면 'next' fallback.",
12
+ "**`--tauri` 옵션이 add-app 에도 동작** — `sh-ui-cli add-app admin --tauri` 또는 MCP `tauri: true` 로 `apps/{name}/src-tauri/` 까지 한 번에 emit. v0.90.0 의 monorepo+tauri 와 동일하게 dev port 가 `tauri.conf.json` 의 `devUrl` 과 `vite.config.ts` 의 `server.port` 양쪽에 자동 박힘. tauri + platform=next 조합은 CLI + MCP 양쪽에서 명시적 Korean 에러.",
13
+ "**Smoke V13 + V14 회귀 가드** — V13 은 vite monorepo create 후 `add-app admin --port 3001 --tauri` (platform 미지정) 가 추론으로 vite 선택 + apps/admin/src-tauri/ + devUrl=3001 + crate name 까지 단언. V14 는 next monorepo 에 `add-app --platform next --tauri` 가 에러 throw 확인. 기존 scenarios 4 + 4b 도 새 platform 추론 분기에 맞춰 prompt 모의 갱신.",
14
+ "**Backward-compat 확인** — Next.js 모노레포에서 기존 `add-app` 호출 흐름은 platform 추론 결과 'next' 가 나와 동일 동작. 깨지는 호출 없음."
15
+ ],
16
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.91.0"
17
+ },
18
+ {
19
+ "version": "0.90.0",
20
+ "date": "2026-05-14",
21
+ "title": "vite + monorepo + tauri 지원 — Tauri 셸이 apps/{name}/src-tauri/ 안",
22
+ "type": "minor",
23
+ "highlights": [
24
+ "**`--platform vite --structure monorepo --tauri` 동작** — v0.88.0 에서 의도적으로 defer 했던 조합 풀림. Tauri 데스크탑 셸이 모노레포 안의 첫 vite 앱 디렉토리 (`apps/{appName}/src-tauri/`) 에 emit. `frontendDist: \"../dist\"` 관계가 그대로 유지되고, Tauri 의 `pnpm dev` / `pnpm build` beforeCommand 도 app 컨텍스트에서 정상 동작.",
25
+ "**Tauri devUrl 이 app port 와 자동 일치** — 모노레포 첫 app port (default 3000) 가 `tauri.conf.json` 의 `devUrl: http://localhost:{port}` + `vite.config.ts` 의 `server.port` 양쪽에 박힘. standalone (5173) 도 그대로 (placeholder 시스템으로 backward-compat). `{{tauri_dev_url}}` 플레이스홀더 신설, `emitTauri({ devPort })` + `patchViteForTauri({ port })` 옵션 시그니처.",
26
+ "**MCP + CLI 진입점에서 거부 가드 제거** — `mcp.mjs` 와 `generator.js` 의 `tauri + structure === 'monorepo'` throw 삭제. `sh_ui_create_project` 의 description 도 `Vite (standalone/monorepo)` 로 갱신. `tauri + platform !== 'vite'` 가드는 그대로 유지 (next/flutter 는 Tauri 통합 안 함).",
27
+ "**docs `/create` UI 도 풀림** — ProjectOptionsForm 의 `disabled={structure !== 'standalone'}` 제거 + composer 의 `--tauri` 가 monorepo 에서도 emit. `/api/template-content` 가 `apps/{name}/src-tauri/*` 경로를 tauri-shell 템플릿에서 resolve. describeTemplate 의 vite-monorepo 분기가 `tauri=true` 일 때 `apps/{appName}/src-tauri/` 그룹 emit.",
28
+ "**Smoke V12 회귀 가드** — vite + monorepo + tauri scaffold 가 (1) apps/web/src-tauri/ 8 파일 emit, (2) tauri.conf.json devUrl = http://localhost:3000, (3) vite.config.ts port = 3000, (4) Cargo.toml crate name = web/web_lib, (5) 루트엔 src-tauri/ 없음 — 모두 단언. v0.88.0 의 V7d (rejection guard) 는 제거 (조합 풀려 더 이상 거부 안 함)."
29
+ ],
30
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.90.0"
31
+ },
5
32
  {
6
33
  "version": "0.89.1",
7
34
  "date": "2026-05-14",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.89.1",
3
+ "version": "0.91.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,7 @@ import { CSS_FRAMEWORK_DEFAULT } from '../constants.js';
35
35
  * @property {'tailwind'|'plain'|'css-modules'} [cssFramework]
36
36
  * @property {string} [projectName]
37
37
  * @property {string} [appName] monorepo 첫 앱 이름. 기본 'web'
38
- * @property {boolean} [tauri] platform=vite + structure=standalone 일 때 Tauri 2.x 셸 같이 emit
38
+ * @property {boolean} [tauri] platform=vite (standalone/monorepo 둘 다) 일 때 Tauri 2.x 셸 같이 emit
39
39
  */
40
40
 
41
41
  /**
@@ -140,6 +140,15 @@ export function describeTemplate(opts = {}) {
140
140
  ),
141
141
  ));
142
142
 
143
+ if (tauri) {
144
+ const tauriTpl = TEMPLATE_MANIFEST['tauri-shell'];
145
+ if (!tauriTpl) {
146
+ throw new Error("Template manifest missing entry for 'tauri-shell'.");
147
+ }
148
+ const tauriFiles = tauriTpl.base.map((p) => `apps/${appName}/src-tauri/${p}`);
149
+ groups.push(makeGroup('tauri', `Tauri 셸 (apps/${appName}/src-tauri/)`, tauriFiles));
150
+ }
151
+
143
152
  return finalize(groups);
144
153
  }
145
154
 
@@ -222,12 +222,6 @@ export async function createProject(options = {}) {
222
222
  `--tauri 옵션 제거 또는 --platform vite 사용.`,
223
223
  );
224
224
  }
225
- if (options.structure === 'monorepo') {
226
- throw new Error(
227
- 'platform=vite + structure=monorepo + tauri=true 는 아직 지원 안 함 (v0.89 후속). ' +
228
- 'standalone 으로 시도하거나 tauri 옵션 제거.',
229
- );
230
- }
231
225
  }
232
226
 
233
227
  // arch 결정 — platform 확정 후. 사용자가 --arch 미지정 시:
@@ -352,14 +346,7 @@ export async function createProject(options = {}) {
352
346
  if (projectType === 'standalone') {
353
347
  await generateViteStandalone(targetDir, projectName, theme, cssFramework, arch, themeBase, { tauri: !!options.tauri });
354
348
  } else {
355
- // monorepo + tauri v0.89 후속 명시적 에러
356
- if (options.tauri) {
357
- throw new Error(
358
- 'platform=vite + structure=monorepo + tauri=true 는 아직 지원 안 함 (v0.89 후속). ' +
359
- 'standalone 으로 시도하거나 tauri 옵션 제거.',
360
- );
361
- }
362
- await generateMonorepo(targetDir, projectName, [], { yes: options.yes, theme, css: cssFramework, arch, themeBase, platform: 'vite' });
349
+ await generateMonorepo(targetDir, projectName, [], { yes: options.yes, theme, css: cssFramework, arch, themeBase, platform: 'vite', tauri: options.tauri });
363
350
  }
364
351
 
365
352
  await finalizeProject(targetDir, { dryRun: options.dryRun });
@@ -377,8 +364,11 @@ export async function createProject(options = {}) {
377
364
  console.log(`\n cd ${projectName}`);
378
365
  console.log(' pnpm install');
379
366
  console.log(' pnpm dev\n');
380
- if (options.tauri && projectType === 'standalone') {
367
+ if (options.tauri) {
381
368
  console.log('Tauri 데스크탑 셸:');
369
+ if (projectType === 'monorepo') {
370
+ console.log(' cd apps/web # 또는 첫 앱 이름');
371
+ }
382
372
  console.log(' pnpm tauri dev # Rust 처음 빌드는 5~10분 — 캐시 후 5~10초');
383
373
  console.log(' (Rust 미설치 시 https://rustup.rs/ 참고)\n');
384
374
  }
@@ -487,6 +477,37 @@ export async function addApp(options = {}) {
487
477
  throw new Error('비대화형 환경(TTY 없음)에서는 name 이 필요합니다.');
488
478
  }
489
479
 
480
+ // platform 결정 — 명시 시 그대로, 미지정 시 기존 apps/* 의 package.json 스캔으로 추론.
481
+ let platform = options.platform;
482
+ if (!platform) {
483
+ platform = await inferMonorepoPlatform(cwd);
484
+ if (!platform) {
485
+ // 모노레포에 아직 앱이 없거나 혼재 — 안전한 디폴트 (next 가 기존 동작).
486
+ // 첫 앱은 user 가 명시하는 게 권장 (interactive prompt).
487
+ if (process.stdin.isTTY) {
488
+ platform = await select({
489
+ message: '플랫폼:',
490
+ choices: [
491
+ { name: 'Next.js', value: 'next' },
492
+ { name: 'Vite (SPA)', value: 'vite' },
493
+ ],
494
+ });
495
+ } else {
496
+ platform = 'next';
497
+ }
498
+ }
499
+ }
500
+ if (platform !== 'next' && platform !== 'vite') {
501
+ throw new Error(
502
+ `add-app 는 platform=next 또는 vite 만 지원 (받은 값: ${platform}). flutter 는 standalone 만 지원.`,
503
+ );
504
+ }
505
+ if (options.tauri && platform !== 'vite') {
506
+ throw new Error(
507
+ `tauri 는 platform=vite 일 때만 지원합니다 (현재 platform=${platform}). --platform vite 사용 또는 tauri 옵션 제거.`,
508
+ );
509
+ }
510
+
490
511
  const appName = validateProjectName(
491
512
  options.name ?? await input({
492
513
  message: '앱 이름:',
@@ -533,9 +554,13 @@ export async function addApp(options = {}) {
533
554
  // arch 는 모노레포가 처음 만들어질 때 정한 값과 같아야 한다. root config 에 별도
534
555
  // 저장 안 해 두므로 일단 DEFAULT_ARCH (fsd) fallback. 향후 root config 에 arch
535
556
  // 박아두고 여기서 읽어오는 흐름으로 개선 가능.
536
- const arch = assertArchPlatformCompat(DEFAULT_ARCH, 'next');
557
+ const arch = assertArchPlatformCompat(DEFAULT_ARCH, platform);
537
558
 
538
- await generateApp(appsDir, appName, port, plugins, arch, css);
559
+ if (platform === 'vite') {
560
+ await generateViteApp(appsDir, appName, port, arch, css, { tauri: !!options.tauri });
561
+ } else {
562
+ await generateApp(appsDir, appName, port, plugins, arch, css);
563
+ }
539
564
 
540
565
  // monorepo 의 새 ui-app 패키지 — tokens-only role + theme 주입 + cssFramework 패치.
541
566
  // generateMonorepo 의 흐름과 정확히 동일.
@@ -548,6 +573,41 @@ export async function addApp(options = {}) {
548
573
  console.log(`\n✅ apps/${appName} 이 추가되었습니다!`);
549
574
  console.log('\n pnpm install');
550
575
  console.log(` pnpm --filter ${appName} dev\n`);
576
+
577
+ if (platform === 'vite' && options.tauri) {
578
+ console.log('Tauri 데스크탑 셸:');
579
+ console.log(` cd apps/${appName}`);
580
+ console.log(' pnpm tauri dev # Rust 처음 빌드는 5~10분 — 캐시 후 5~10초');
581
+ console.log(' (Rust 미설치 시 https://rustup.rs/ 참고)\n');
582
+ }
583
+ }
584
+
585
+ /**
586
+ * 모노레포의 기존 apps/* 들 package.json 을 스캔해 platform 추론.
587
+ * - 모든 앱이 vite — 'vite'
588
+ * - 모든 앱이 next — 'next'
589
+ * - 혼재 또는 다른 조합 — null (user 가 명시해야 함)
590
+ * - apps/ 가 없거나 비었으면 null
591
+ */
592
+ async function inferMonorepoPlatform(cwd) {
593
+ const appsDir = path.resolve(cwd, 'apps');
594
+ if (!(await fs.pathExists(appsDir))) return null;
595
+ const entries = await fsp.readdir(appsDir, { withFileTypes: true });
596
+ const apps = entries.filter((e) => e.isDirectory());
597
+ if (apps.length === 0) return null;
598
+
599
+ const platforms = new Set();
600
+ for (const app of apps) {
601
+ const pkgPath = path.join(appsDir, app.name, 'package.json');
602
+ if (!(await fs.pathExists(pkgPath))) continue;
603
+ const pkg = await fs.readJson(pkgPath);
604
+ const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
605
+ if (deps.next) platforms.add('next');
606
+ else if (deps.vite) platforms.add('vite');
607
+ }
608
+
609
+ if (platforms.size === 1) return [...platforms][0];
610
+ return null;
551
611
  }
552
612
 
553
613
  // ─── Add component to ui packages ───
@@ -798,8 +858,8 @@ async function generateViteStandalone(targetDir, projectName, theme, css, arch,
798
858
 
799
859
  // Tauri 셸 emit (옵션) — vite SPA + native window. standalone 만 v1 지원.
800
860
  if (tauri) {
801
- await emitTauri(targetDir, projectName);
802
- await patchViteForTauri(targetDir);
861
+ await emitTauri(targetDir, projectName, { devPort: 5173 });
862
+ await patchViteForTauri(targetDir, { port: 5173 });
803
863
  }
804
864
  }
805
865
 
@@ -812,7 +872,7 @@ async function generateViteStandalone(targetDir, projectName, theme, css, arch,
812
872
  *
813
873
  * generateViteStandalone 에서 tauri: true 인 경우 호출. monorepo+tauri 는 v0.89 후속.
814
874
  */
815
- async function emitTauri(targetDir, projectName) {
875
+ async function emitTauri(targetDir, projectName, { devPort = 5173 } = {}) {
816
876
  const srcTauriDir = path.join(targetDir, 'src-tauri');
817
877
  await fs.copy(path.join(TEMPLATES_DIR, 'tauri-shell'), srcTauriDir);
818
878
 
@@ -824,6 +884,7 @@ async function emitTauri(targetDir, projectName) {
824
884
 
825
885
  await replaceInAllFiles(srcTauriDir, '{{tauri_crate_name}}', tauriCrateName);
826
886
  await replaceInAllFiles(srcTauriDir, '{{project_name}}', projectName);
887
+ await replaceInAllFiles(srcTauriDir, '{{tauri_dev_url}}', `http://localhost:${devPort}`);
827
888
  }
828
889
 
829
890
  /**
@@ -837,7 +898,7 @@ async function emitTauri(targetDir, projectName) {
837
898
  * 이라 안전. 후속 task 에서 arch-specific vite.config.ts overlay 가 생기면 이 자리에서 머지 전략
838
899
  * 필요 (현재는 단순 overwrite).
839
900
  */
840
- async function patchViteForTauri(targetDir) {
901
+ async function patchViteForTauri(targetDir, { port = 5173 } = {}) {
841
902
  const pkgPath = path.join(targetDir, 'package.json');
842
903
  const pkg = await fs.readJson(pkgPath);
843
904
 
@@ -872,7 +933,7 @@ export default defineConfig({
872
933
  plugins: [react(), tailwindcss(), tsconfigPaths()],
873
934
  clearScreen: false,
874
935
  server: {
875
- port: 5173,
936
+ port: ${port},
876
937
  strictPort: true,
877
938
  host: false,
878
939
  },
@@ -897,7 +958,7 @@ export default defineConfig({
897
958
  }
898
959
  }
899
960
 
900
- async function generateMonorepo(targetDir, projectName, plugins, { yes = false, theme, css, arch, themeBase, platform = 'next' } = {}) {
961
+ async function generateMonorepo(targetDir, projectName, plugins, { yes = false, theme, css, arch, themeBase, platform = 'next', tauri = false } = {}) {
901
962
  await fs.copy(path.join(TEMPLATES_DIR, 'monorepo'), targetDir);
902
963
 
903
964
  // Update root package.json
@@ -929,7 +990,7 @@ async function generateMonorepo(targetDir, projectName, plugins, { yes = false,
929
990
 
930
991
  const appsDir = path.join(targetDir, 'apps', appName);
931
992
  if (platform === 'vite') {
932
- await generateViteApp(appsDir, appName, port, arch, css);
993
+ await generateViteApp(appsDir, appName, port, arch, css, { tauri });
933
994
  } else {
934
995
  await generateApp(appsDir, appName, port, plugins, arch, css);
935
996
  }
@@ -1030,7 +1091,7 @@ async function generateApp(targetDir, appName, port, plugins, arch, css = 'tailw
1030
1091
  }
1031
1092
  }
1032
1093
 
1033
- async function generateViteApp(targetDir, appName, port, arch, css = 'tailwind') {
1094
+ async function generateViteApp(targetDir, appName, port, arch, css = 'tailwind', { tauri = false } = {}) {
1034
1095
  // 베이스 (arch-neutral) + arch 오버레이 — generateApp 과 동일 패턴.
1035
1096
  await fs.copy(path.join(TEMPLATES_DIR, 'vite-app'), targetDir, {
1036
1097
  filter: (src) => !src.includes(`${path.sep}_arch${path.sep}`) && !src.endsWith(`${path.sep}_arch`),
@@ -1089,6 +1150,15 @@ async function generateViteApp(targetDir, appName, port, arch, css = 'tailwind')
1089
1150
  if (await fs.pathExists(uiPkgDir)) {
1090
1151
  await applyCssFrameworkVariant(uiPkgDir, css, { isMonorepo: true, plugins: [], arch, isUiPackage: true });
1091
1152
  }
1153
+
1154
+ // tauri 가 켜져 있으면 이 app 안에 src-tauri/ shell 을 떨어뜨린다 (v0.90.0+).
1155
+ // standalone 과 달리 monorepo 에서는 app 단위로 별도 dev port (default 3000) 가 있고,
1156
+ // tauri 의 devUrl 이 그 port 와 일치해야 한다. frontendDist 는 src-tauri 기준 `../dist`.
1157
+ if (tauri) {
1158
+ const devPort = Number(port) || 3000;
1159
+ await emitTauri(targetDir, appName, { devPort });
1160
+ await patchViteForTauri(targetDir, { port: devPort });
1161
+ }
1092
1162
  }
1093
1163
 
1094
1164
  /**
@@ -18,7 +18,7 @@ export const HELP_TEXT = `sh-ui create — sh-ui 프로젝트 스캐폴드 (Next
18
18
 
19
19
  사용법:
20
20
  sh-ui create [name] [options]
21
- sh-ui create add-app [name] [--port <n>] [--plugins ..] [--theme ..] [--css ..]
21
+ sh-ui create add-app [name] [--port <n>] [--platform <next|vite>] [--plugins ..] [--theme ..] [--css ..] [--tauri]
22
22
  sh-ui create add-component <name> [--app <name>]
23
23
 
24
24
  옵션:
@@ -73,6 +73,8 @@ export async function runCreate(rest) {
73
73
  plugins: flags.plugins,
74
74
  theme: flags.theme,
75
75
  css: flags.css,
76
+ platform: flags.platform,
77
+ tauri: flags.tauri,
76
78
  });
77
79
  } else if (command === 'add-component') {
78
80
  // 호환 별칭 — 신규 진입점은 `sh-ui add <name>` (bin/sh-ui.mjs 가 walk-up 으로 라우팅).
package/src/mcp.mjs CHANGED
@@ -367,7 +367,7 @@ export async function startMcpServer() {
367
367
  {
368
368
  description:
369
369
  "빈 폴더에 sh-ui 프로젝트 스캐폴드 — Next.js (standalone/monorepo) | Vite (standalone/monorepo) | Flutter. " +
370
- "+ vite + standalone 인 경우 `tauri: true` 로 Tauri 2.x 데스크탑 셸 (`src-tauri/`) 까지 한 번에 emit (Rust toolchain 필요). " +
370
+ "+ vite 인 경우 `tauri: true` 로 Tauri 2.x 데스크탑 셸 (`src-tauri/`) 까지 한 번에 emit (Rust toolchain 필요). " +
371
371
  `FSD 폴더 구조 + 토큰 + sh-ui.config.json 일괄 생성. 사용자가 '새 프로젝트' / '빈 폴더' / '스캐폴드부터' 류 요청을 하면 이 툴 사용 (Bash 로 npx ${cliName} create 직접 호출보다 우선). ` +
372
372
  "**단일 진입점** — theme/plugins/cssFramework/structure 모두 호출 시점에 정해서 한 번에 박는다. 호출 후 sh-ui.config.json/tokens.css 를 손으로 패치하지 말 것 (다음 재스캐폴드 시 유실). " +
373
373
  "산출물: theme 인자가 프리셋이면 sh-ui.config.json 의 theme.base 가 그 이름, base64 면 'custom'. paths.styles · paths.tokens 도 자동 박혀서 sh_ui_add_component 가 사후 패치 없이 동작.",
@@ -397,9 +397,10 @@ export async function startMcpServer() {
397
397
  .describe("기존 디렉토리 덮어쓰기. 기본 false (안전)"),
398
398
  tauri: z.boolean().optional()
399
399
  .describe(
400
- "Tauri 2.x 데스크탑 셸 (`src-tauri/`) 함께 emit. platform=vite + structure=standalone 일 때만 지원. " +
400
+ "Tauri 2.x 데스크탑 셸 (`src-tauri/`) 함께 emit. platform=vite (standalone/monorepo 둘 다) 일 때만 지원. " +
401
+ "monorepo 의 경우 src-tauri/ 는 apps/{appName}/ 안에 emit, devUrl 은 해당 app 의 dev port 와 자동 동기화. " +
401
402
  "Rust toolchain (`cargo`/`rustc`) 가 시스템에 설치되어 있어야 첫 `pnpm tauri dev` 가 동작. " +
402
- "기본 false. monorepo + tauri 는 v0.89 후속.",
403
+ "기본 false.",
403
404
  ),
404
405
  },
405
406
  },
@@ -431,15 +432,6 @@ export async function startMcpServer() {
431
432
  }],
432
433
  };
433
434
  }
434
- if (input.tauri && input.structure === "monorepo") {
435
- return {
436
- isError: true,
437
- content: [{
438
- type: "text",
439
- text: "platform=vite + structure=monorepo + tauri=true 는 아직 지원 안 함 (v0.89 후속). standalone 으로 시도하거나 tauri 옵션 제거.",
440
- }],
441
- };
442
- }
443
435
  const targetParent = resolveCwd(input);
444
436
  const targetDir = resolve(targetParent, input.name);
445
437
  if (existsSync(targetDir) && !input.force) {
@@ -480,10 +472,11 @@ export async function startMcpServer() {
480
472
  "sh_ui_add_app",
481
473
  {
482
474
  description:
483
- "기존 모노레포에 새 Next.js 추가 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 동시 생성. " +
475
+ "기존 모노레포에 새 앱 (Next.js 또는 Vite) 추가 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 동시 생성. " +
484
476
  "사용자가 '앱 추가' / 'monorepo 에 새 앱' / 'add admin app' 류 요청을 하면 이 툴 사용 (Bash 로 npx " + cliName + " add-app 직접 호출보다 우선). " +
485
477
  "v0.65+ 레이아웃 준수 — ui-{name} 은 tokens-only role, 컴포넌트는 sibling ui-core 가 SoT. " +
486
- "theme/css 는 새 ui-app 에만 적용 (다른 앱 영향 없음). monorepo 가 아니면 (pnpm-workspace.yaml 없음) 에러.",
478
+ "theme/css 는 새 ui-app 에만 적용 (다른 앱 영향 없음). monorepo 가 아니면 (pnpm-workspace.yaml 없음) 에러. " +
479
+ "platform 미지정 시 기존 apps/* 스캔해 추론 (모든 앱이 같은 플랫폼이면 그것으로). vite + tauri:true 면 apps/{name}/src-tauri/ 도 함께 emit.",
487
480
  inputSchema: {
488
481
  name: z.string().min(1)
489
482
  .describe("앱 이름 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 디렉토리명. 영숫자 + 하이픈."),
@@ -495,6 +488,10 @@ export async function startMcpServer() {
495
488
  .describe(`프리셋 이름 (${THEME_PRESETS_LIST}) 또는 base64 테마 코드. 새 ui-app 의 tokens.css 에만 주입.`),
496
489
  cssFramework: z.enum(CSS_FRAMEWORKS).optional()
497
490
  .describe("CSS 프레임워크. 기본 plain. 새 앱의 컴포넌트 변종 결정 — 같은 모노레포 내 다른 앱과 다른 값 가능."),
491
+ platform: z.enum(["next", "vite"]).optional()
492
+ .describe("플랫폼 — next | vite. 미지정 시 기존 apps/* 의 deps 로 추론 (모든 앱이 같은 플랫폼이면 그것으로, 혼재면 명시 필요)."),
493
+ tauri: z.boolean().optional()
494
+ .describe("Tauri 2.x 데스크탑 셸 — platform=vite 일 때만 의미. apps/{name}/src-tauri/ 에 emit. 기본 false."),
498
495
  cwd: z.string().optional()
499
496
  .describe("모노레포 루트 (pnpm-workspace.yaml 있는 곳). 기본 process.cwd()"),
500
497
  },
@@ -505,6 +502,15 @@ export async function startMcpServer() {
505
502
  } catch (e) {
506
503
  return { isError: true, content: [{ type: "text", text: e.message }] };
507
504
  }
505
+ if (input.tauri && input.platform === "next") {
506
+ return {
507
+ isError: true,
508
+ content: [{
509
+ type: "text",
510
+ text: "tauri 는 platform=vite 일 때만 지원합니다. --platform vite 사용 또는 tauri 옵션 제거.",
511
+ }],
512
+ };
513
+ }
508
514
  const text = await captureConsole(() =>
509
515
  addApp({
510
516
  name: input.name,
@@ -512,6 +518,8 @@ export async function startMcpServer() {
512
518
  plugins: input.plugins,
513
519
  theme: input.theme,
514
520
  css: input.cssFramework,
521
+ platform: input.platform,
522
+ tauri: input.tauri,
515
523
  cwd: resolveCwd(input),
516
524
  }),
517
525
  );
@@ -5,7 +5,7 @@
5
5
  "identifier": "app.{{tauri_crate_name}}.dev",
6
6
  "build": {
7
7
  "beforeDevCommand": "pnpm dev",
8
- "devUrl": "http://localhost:5173",
8
+ "devUrl": "{{tauri_dev_url}}",
9
9
  "beforeBuildCommand": "pnpm build",
10
10
  "frontendDist": "../dist"
11
11
  },