sh-ui-cli 0.115.0 → 0.116.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/bin/sh-ui.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { init } from "../src/init.mjs";
2
+ import { init, HELP_TEXT as INIT_HELP } from "../src/init.mjs";
3
3
  import { add } from "../src/add.mjs";
4
4
  import { list } from "../src/list.mjs";
5
5
  import { remove } from "../src/remove.mjs";
6
6
  import { findShUiContext } from "../src/resolve-context.mjs";
7
+ import { suggest } from "../src/levenshtein.mjs";
8
+ import { KNOWN_COMMANDS } from "../src/commands.mjs";
7
9
 
8
10
  const [, , cmd, ...rest] = process.argv;
9
11
 
@@ -36,6 +38,9 @@ const usage = `사용법:
36
38
  sh-ui mcp MCP 서버(stdio) 시작 — IDE-내 AI용
37
39
  sh-ui mcp init --client <name> IDE MCP 설정 파일에 sh-ui 엔트리 자동 추가
38
40
  (claude-code | cursor | claude-desktop | codex)
41
+
42
+ 각 명령의 상세 옵션은 \`sh-ui <command> --help\` 로 확인.
43
+
39
44
  옵션:
40
45
  --skip-install (add, rename-app) 외부 패키지 자동 설치 생략
41
46
  --diff (add) 파일을 쓰지 않고 변경 내역만 출력
@@ -57,9 +62,18 @@ try {
57
62
  break;
58
63
  }
59
64
  case "init":
65
+ if (rest.includes("--help") || rest.includes("-h")) {
66
+ console.log(INIT_HELP);
67
+ break;
68
+ }
60
69
  await init({ cwd: process.cwd(), args: rest });
61
70
  break;
62
71
  case "add": {
72
+ if (rest.includes("--help") || rest.includes("-h")) {
73
+ const { HELP_TEXT } = await import("../src/add.mjs");
74
+ console.log(HELP_TEXT);
75
+ break;
76
+ }
63
77
  const skipInstall = rest.includes("--skip-install");
64
78
  const diffMode = rest.includes("--diff");
65
79
  const force = rest.includes("--force");
@@ -107,11 +121,21 @@ try {
107
121
  break;
108
122
  }
109
123
  case "list": {
124
+ if (rest.includes("--help") || rest.includes("-h")) {
125
+ const { HELP_TEXT } = await import("../src/list.mjs");
126
+ console.log(HELP_TEXT);
127
+ break;
128
+ }
110
129
  const all = rest.includes("--all");
111
130
  await list({ cwd: process.cwd(), all });
112
131
  break;
113
132
  }
114
133
  case "doctor": {
134
+ if (rest.includes("--help") || rest.includes("-h")) {
135
+ const { HELP_TEXT } = await import("../src/doctor.mjs");
136
+ console.log(HELP_TEXT);
137
+ break;
138
+ }
115
139
  const { doctor } = await import("../src/doctor.mjs");
116
140
  const { existsSync, readdirSync } = await import("node:fs");
117
141
  const { resolve } = await import("node:path");
@@ -157,12 +181,22 @@ try {
157
181
  break;
158
182
  }
159
183
  case "upgrade-cli": {
184
+ if (rest.includes("--help") || rest.includes("-h")) {
185
+ const { HELP_TEXT } = await import("../src/upgrade-cli.mjs");
186
+ console.log(HELP_TEXT);
187
+ break;
188
+ }
160
189
  const apply = rest.includes("--apply");
161
190
  const { runUpgradeCli } = await import("../src/upgrade-cli.mjs");
162
191
  await runUpgradeCli({ cwd: process.cwd(), apply });
163
192
  break;
164
193
  }
165
194
  case "theme": {
195
+ if (rest.includes("--help") || rest.includes("-h")) {
196
+ const { HELP_TEXT } = await import("../src/theme-extract.mjs");
197
+ console.log(HELP_TEXT);
198
+ break;
199
+ }
166
200
  const sub = rest[0];
167
201
  const flags = rest.slice(1);
168
202
  if (sub === "extract") {
@@ -177,6 +211,11 @@ try {
177
211
  break;
178
212
  }
179
213
  case "tokens": {
214
+ if (rest.includes("--help") || rest.includes("-h")) {
215
+ const { HELP_TEXT } = await import("../src/tokens-cmd.mjs");
216
+ console.log(HELP_TEXT);
217
+ break;
218
+ }
180
219
  // sh-ui tokens diff
181
220
  // sh-ui tokens upgrade --apply | --replace
182
221
  const sub = rest[0];
@@ -210,6 +249,12 @@ try {
210
249
  case "mcp": {
211
250
  // `sh-ui mcp init ...` → 설정 파일에 엔트리 추가
212
251
  // `sh-ui mcp` → MCP 서버 시작
252
+ // 단, `sh-ui mcp --help` 는 mcp HELP_TEXT 출력 (mcp init 의 인자 처리는 mcp-init.mjs 가 담당).
253
+ if (rest[0] !== "init" && (rest.includes("--help") || rest.includes("-h"))) {
254
+ const { HELP_TEXT } = await import("../src/mcp.mjs");
255
+ console.log(HELP_TEXT);
256
+ break;
257
+ }
213
258
  if (rest[0] === "init") {
214
259
  const { mcpInit } = await import("../src/mcp-init.mjs");
215
260
  await mcpInit({ cwd: process.cwd(), args: rest.slice(1) });
@@ -220,6 +265,11 @@ try {
220
265
  break;
221
266
  }
222
267
  case "rename-app": {
268
+ if (rest.includes("--help") || rest.includes("-h")) {
269
+ const { HELP_TEXT } = await import("../src/rename-app.mjs");
270
+ console.log(HELP_TEXT);
271
+ break;
272
+ }
223
273
  const yes = rest.includes("--yes");
224
274
  const dryRun = rest.includes("--dry-run");
225
275
  const skipInstall = rest.includes("--skip-install");
@@ -235,6 +285,11 @@ try {
235
285
  break;
236
286
  }
237
287
  case "migrate": {
288
+ if (rest.includes("--help") || rest.includes("-h")) {
289
+ const { HELP_TEXT } = await import("../src/migrate-bundled.mjs");
290
+ console.log(HELP_TEXT);
291
+ break;
292
+ }
238
293
  // sh-ui migrate bundled [--apply] [--bundle <path>]
239
294
  const sub = rest[0];
240
295
  const flags = rest.slice(1);
@@ -251,6 +306,11 @@ try {
251
306
  break;
252
307
  }
253
308
  case "migrate-v065": {
309
+ if (rest.includes("--help") || rest.includes("-h")) {
310
+ const { HELP_TEXT } = await import("../src/migrate-v065.mjs");
311
+ console.log(HELP_TEXT);
312
+ break;
313
+ }
254
314
  const apply = rest.includes("--apply");
255
315
  const skipImportRewrite = rest.includes("--skip-import-rewrite");
256
316
  const { migrateToV065 } = await import("../src/migrate-v065.mjs");
@@ -264,6 +324,11 @@ try {
264
324
  }
265
325
  case "remove":
266
326
  case "rm": {
327
+ if (rest.includes("--help") || rest.includes("-h")) {
328
+ const { HELP_TEXT } = await import("../src/remove.mjs");
329
+ console.log(HELP_TEXT);
330
+ break;
331
+ }
267
332
  const force = rest.includes("--force");
268
333
  const dryRun = rest.includes("--dry-run");
269
334
  const names = rest.filter((a) => !a.startsWith("--"));
@@ -280,10 +345,12 @@ try {
280
345
  case "--help":
281
346
  console.log(usage);
282
347
  break;
283
- default:
284
- console.error(`알 없는 명령: ${cmd}\n`);
348
+ default: {
349
+ const hits = suggest(cmd, KNOWN_COMMANDS);
350
+ console.error(`알 수 없는 명령: ${cmd}` + (hits.length ? ` — 혹시 ${hits.join(", ")}?` : "") + "\n");
285
351
  console.error(usage);
286
352
  process.exit(1);
353
+ }
287
354
  }
288
355
  } catch (err) {
289
356
  console.error(`✗ ${err.message}`);
@@ -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.116.0",
7
+ "date": "2026-06-17",
8
+ "title": "CLI 발견성 — 서브명령 --help + 오타 추천",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "모든 서브명령에 --help — init·add·remove·doctor·tokens·theme·migrate·rename-app·upgrade-cli·mcp 전용 도움말",
12
+ "did-you-mean — 없는 컴포넌트/명령 입력 시 가까운 후보를 '혹시 …?' 로 제안",
13
+ "의존성 없는 levenshtein 유틸 내장 (packages/cli/src/levenshtein.mjs)"
14
+ ],
15
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.116.0"
16
+ },
5
17
  {
6
18
  "version": "0.115.0",
7
19
  "date": "2026-06-01",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.115.0",
3
+ "version": "0.116.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/add.mjs CHANGED
@@ -5,6 +5,7 @@ import { pathToFileURL } from "node:url";
5
5
  import { spawn } from "node:child_process";
6
6
  import { select } from "@inquirer/prompts";
7
7
  import { formatUnifiedDiff } from "./diff.mjs";
8
+ import { suggest } from "./levenshtein.mjs";
8
9
  import { getRegistryRoot, getTokensRoot, getPeerVersionsPath } from "./paths.mjs";
9
10
  import { THEME_BASES } from "./constants.js";
10
11
  import {
@@ -20,6 +21,25 @@ import {
20
21
  rewriteCrossComponentImports,
21
22
  } from "./css-bundle.mjs";
22
23
 
24
+ export const HELP_TEXT = `sh-ui add — 컴포넌트 소스를 프로젝트로 복사 + 필요한 패키지 자동 설치
25
+
26
+ 사용법:
27
+ sh-ui add <component...>
28
+ sh-ui add tokens 설정 기반 토큰 파일 생성 (특수값)
29
+
30
+ 옵션:
31
+ --skip-install 외부 패키지 자동 설치 생략
32
+ --diff 파일을 쓰지 않고 변경 내역(unified diff)만 출력
33
+ --force 기존 파일을 모두 덮어쓰기 (prompt 없음)
34
+ --keep 기존 파일을 모두 유지 (prompt 없음)
35
+ --app <name> monorepo 라우팅 시 대상 ui-{name} 명시
36
+
37
+ 예:
38
+ sh-ui add button
39
+ sh-ui add button card --diff
40
+ sh-ui add tokens
41
+ `;
42
+
23
43
  /**
24
44
  * 기존 파일과 registry 파일 내용이 다를 때 keep/overwrite 결정.
25
45
  * strategy 가 "prompt" 면 사용자에게 묻고, 그 외엔 즉시 결정.
@@ -336,6 +356,16 @@ function effectiveFramework(entry, cssFramework) {
336
356
  return hasVariant ? cssFramework : "plain";
337
357
  }
338
358
 
359
+ /** 컴포넌트 not-found 에러 메시지. 가까운 후보가 있으면 "혹시 …?" 를 덧붙인다. */
360
+ export function buildNotFoundMessage(name, platform, candidates) {
361
+ const hits = suggest(name, candidates);
362
+ const hint = hits.length ? ` 혹시 ${hits.join(", ")}?` : "";
363
+ return (
364
+ `'${name}' 컴포넌트를 ${platform} 레지스트리에서 찾을 수 없습니다.${hint}` +
365
+ ` 전체 목록: sh-ui list --all`
366
+ );
367
+ }
368
+
339
369
  async function addComponent(name, config, cwd, installed, pendingDeps, diffMode, summary, conflictResolver, validationCtx) {
340
370
  const registryRoot = getRegistryRoot(config.platform);
341
371
  const registry = JSON.parse(
@@ -344,7 +374,7 @@ async function addComponent(name, config, cwd, installed, pendingDeps, diffMode,
344
374
  const entry = registry.components?.[name];
345
375
  if (!entry) {
346
376
  throw new Error(
347
- `'${name}' 컴포넌트를 ${config.platform} 레지스트리에서 찾을 수 없습니다.`,
377
+ buildNotFoundMessage(name, config.platform, Object.keys(registry.components ?? {})),
348
378
  );
349
379
  }
350
380
 
@@ -0,0 +1,6 @@
1
+ // CLI 최상위 명령 목록 — bin 라우터와 did-you-mean 추천이 공유.
2
+ export const KNOWN_COMMANDS = [
3
+ "create", "init", "add", "list", "doctor", "upgrade-cli",
4
+ "theme", "tokens", "mcp", "rename-app", "migrate", "migrate-v065",
5
+ "remove", "rm",
6
+ ];
@@ -678,6 +678,10 @@ async function inferMonorepoPlatform(cwd) {
678
678
  * @param {string} [options.onConflict] 'prompt' | 'keep' | 'overwrite'
679
679
  */
680
680
  export async function addComponent(options = {}) {
681
+ // monorepo 라우팅은 컴포넌트별 registry 조회를 직접 하지 않고 add() 로 위임한다.
682
+ // 따라서 not-found 처리(오타 추천 포함)는 add.mjs 의 addComponent 가 공유 헬퍼
683
+ // buildNotFoundMessage 로 던지는 메시지를 그대로 쓴다 — standalone 경로와 동일,
684
+ // 메시지 로직 중복 없음 (DRY).
681
685
  const { add } = await import('../add.mjs');
682
686
 
683
687
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
package/src/doctor.mjs CHANGED
@@ -18,6 +18,20 @@ import {
18
18
  findMissingTokens,
19
19
  } from "./tokens-validate.mjs";
20
20
 
21
+ export const HELP_TEXT = `sh-ui doctor — 프로젝트 정합성 점검
22
+
23
+ 사용법:
24
+ sh-ui doctor
25
+
26
+ 점검 항목:
27
+ config sh-ui.config.json 존재·필드 유효성
28
+ tokens tokens.css 정의·필요 변수 누락 여부
29
+ 설치된 컴포넌트 소스/스타일 파일 정합성
30
+
31
+ 예:
32
+ sh-ui doctor
33
+ `;
34
+
21
35
  const ICON = { ok: "✓", warn: "⚠", fail: "✗" };
22
36
 
23
37
  class Report {
package/src/init.mjs CHANGED
@@ -13,6 +13,25 @@ import {
13
13
  CSS_FRAMEWORKS_PLANNED,
14
14
  } from "./constants.js";
15
15
 
16
+ export const HELP_TEXT = `sh-ui init — sh-ui.config.json 생성 (기존 프로젝트에 sh-ui 얹기)
17
+
18
+ 사용법:
19
+ sh-ui init [options]
20
+
21
+ 옵션:
22
+ --platform <react|flutter> 타겟 플랫폼
23
+ --base <neutral|zinc|...> 색 베이스
24
+ --radius <none|sm|md|lg|xl|full> 모서리 반경
25
+ --mode <light|dark|light-dark> 색 모드
26
+ --cssFramework <plain|tailwind|css-modules> CSS 전략 (vanilla-extract 예정)
27
+ --force 기존 sh-ui.config.json 덮어쓰기
28
+ --yes 대화형 프롬프트 생략 (기본값 채택)
29
+
30
+ 예:
31
+ sh-ui init
32
+ sh-ui init --platform react --base neutral --mode light-dark --yes
33
+ `;
34
+
16
35
  const CHOICES = {
17
36
  platform: INIT_PLATFORMS,
18
37
  base: THEME_BASES,
@@ -0,0 +1,36 @@
1
+ // 의존성 없는 편집거리 + 후보 제안. diff.mjs 가 LCS 를 자체 구현한 관행을 따른다.
2
+
3
+ /** 두 문자열의 Levenshtein 편집거리 (삽입/삭제/치환 비용 1). */
4
+ export function levenshtein(a, b) {
5
+ const m = a.length;
6
+ const n = b.length;
7
+ if (m === 0) return n;
8
+ if (n === 0) return m;
9
+ let prev = Array.from({ length: n + 1 }, (_, j) => j);
10
+ let curr = new Array(n + 1);
11
+ for (let i = 1; i <= m; i++) {
12
+ curr[0] = i;
13
+ for (let j = 1; j <= n; j++) {
14
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
15
+ curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
16
+ }
17
+ [prev, curr] = [curr, prev];
18
+ }
19
+ return prev[n];
20
+ }
21
+
22
+ /**
23
+ * input 과 가까운 candidates 를 거리 오름차순으로 반환.
24
+ * @param {string} input
25
+ * @param {string[]} candidates
26
+ * @param {{max?: number, maxDistance?: number}} [opts]
27
+ * @returns {string[]} 거리 <= maxDistance 인 후보 상위 max 개 (없으면 빈 배열)
28
+ */
29
+ export function suggest(input, candidates, { max = 3, maxDistance = 2 } = {}) {
30
+ return candidates
31
+ .map((name) => ({ name, dist: levenshtein(input, name) }))
32
+ .filter((c) => c.dist <= maxDistance)
33
+ .sort((a, b) => a.dist - b.dist || a.name.localeCompare(b.name))
34
+ .slice(0, max)
35
+ .map((c) => c.name);
36
+ }
package/src/list.mjs CHANGED
@@ -3,6 +3,19 @@ import { existsSync } from "node:fs";
3
3
  import { resolve, relative } from "node:path";
4
4
  import { getRegistryRoot } from "./paths.mjs";
5
5
 
6
+ export const HELP_TEXT = `sh-ui list — 현재 설치된 컴포넌트 목록 표시
7
+
8
+ 사용법:
9
+ sh-ui list
10
+
11
+ 옵션:
12
+ --all 설치되지 않은 컴포넌트까지 모두 표시
13
+
14
+ 예:
15
+ sh-ui list
16
+ sh-ui list --all
17
+ `;
18
+
6
19
  function resolveDest(template, config) {
7
20
  return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (m, key) => {
8
21
  const v = config.paths?.[key];
package/src/mcp.mjs CHANGED
@@ -56,6 +56,20 @@ import { THEME_PRESET_NAMES } from "./create/theme/presets.js";
56
56
  import { decodeTheme } from "./create/theme/decode.js";
57
57
  import { encodeTheme } from "./create/theme/encode.js";
58
58
 
59
+ export const HELP_TEXT = `sh-ui mcp — IDE-내 AI 용 MCP 서버 / 설정
60
+
61
+ 사용법:
62
+ sh-ui mcp MCP 서버(stdio) 시작
63
+ sh-ui mcp init --client <name> IDE MCP 설정 파일에 sh-ui 엔트리 자동 추가
64
+
65
+ 옵션 (mcp init):
66
+ --client <name> claude-code | cursor | claude-desktop | codex
67
+
68
+ 예:
69
+ sh-ui mcp
70
+ sh-ui mcp init --client claude-code
71
+ `;
72
+
59
73
  const PLATFORMS = INIT_PLATFORMS;
60
74
  const BASES = THEME_BASES;
61
75
  const RADII = THEME_RADII;
@@ -16,6 +16,20 @@ import { existsSync } from "node:fs";
16
16
  import { resolve, relative, dirname, join } from "node:path";
17
17
  import { upsertSection, stripStylesImport } from "./css-bundle.mjs";
18
18
 
19
+ export const HELP_TEXT = `sh-ui migrate bundled — cssStrategy=bundled 로 전환
20
+
21
+ 사용법:
22
+ sh-ui migrate bundled per-component styles.css → 단일 sh-ui-components.css
23
+
24
+ 옵션:
25
+ --apply 실제 적용 (미지정 시 미리보기)
26
+ --bundle <path> 번들 CSS 파일 경로 명시
27
+
28
+ 예:
29
+ sh-ui migrate bundled
30
+ sh-ui migrate bundled --apply
31
+ `;
32
+
19
33
  async function loadConfig(cwd) {
20
34
  const configPath = resolve(cwd, "sh-ui.config.json");
21
35
  if (!existsSync(configPath)) {
@@ -14,6 +14,20 @@ import * as fsp from "node:fs/promises";
14
14
  import path from "node:path";
15
15
  import { createHash } from "node:crypto";
16
16
 
17
+ export const HELP_TEXT = `sh-ui migrate-v065 — v0.64.x → v0.65 자동 마이그레이션
18
+
19
+ 사용법:
20
+ sh-ui migrate-v065 ui-app 컴포넌트를 ui-core 단일 SoT 로 dedup 이동
21
+
22
+ 옵션:
23
+ --dry-run 변경 plan 만 출력 (기본값 — 미지정 시 동작)
24
+ --apply 실제 적용
25
+
26
+ 예:
27
+ sh-ui migrate-v065
28
+ sh-ui migrate-v065 --apply
29
+ `;
30
+
17
31
  const COMPONENT_KINDS = ["components", "hooks", "lib"];
18
32
 
19
33
  async function fileExists(p) {
package/src/remove.mjs CHANGED
@@ -2,6 +2,21 @@ import { readFile, rm, rmdir, readdir, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import { dirname, resolve, relative } from "node:path";
4
4
  import { getRegistryRoot } from "./paths.mjs";
5
+
6
+ export const HELP_TEXT = `sh-ui remove — 설치된 컴포넌트 파일 삭제
7
+
8
+ 사용법:
9
+ sh-ui remove <component...>
10
+
11
+ 옵션:
12
+ --force 사용자가 수정한 파일도 삭제
13
+ --dry-run 변경 대상만 출력하고 실제로는 삭제하지 않음
14
+
15
+ 예:
16
+ sh-ui remove button
17
+ sh-ui remove button card --dry-run
18
+ `;
19
+
5
20
  import {
6
21
  removeSection,
7
22
  isStyleFile,
@@ -19,6 +19,21 @@ import { spawn } from "node:child_process";
19
19
  import { join, relative, resolve } from "node:path";
20
20
  import { createInterface } from "node:readline/promises";
21
21
 
22
+ export const HELP_TEXT = `sh-ui rename-app — monorepo 의 앱 이름 일괄 변경
23
+
24
+ 사용법:
25
+ sh-ui rename-app <old> <new> apps/<old>/, ui-<old>/ 디렉토리 + 모든 import/path 패턴
26
+
27
+ 옵션:
28
+ --yes 대화형 확인 생략
29
+ --dry-run 변경 대상만 출력하고 실제로는 변경하지 않음
30
+ --skip-install 변경 후 pnpm install 생략
31
+
32
+ 예:
33
+ sh-ui rename-app web admin
34
+ sh-ui rename-app web admin --dry-run
35
+ `;
36
+
22
37
  /** 텍스트 파일로 처리할 확장자 (전체 파일을 읽어 치환). */
23
38
  const TEXT_EXT = new Set([
24
39
  ".ts", ".tsx", ".js", ".mjs", ".cjs",
@@ -17,6 +17,19 @@ import { resolve, relative } from "node:path";
17
17
  import { parseBlocks } from "./tokens-diff.mjs";
18
18
  import { encodeTheme } from "./create/theme/encode.js";
19
19
 
20
+ export const HELP_TEXT = `sh-ui theme — 현재 토큰을 테마 코드로 추출
21
+
22
+ 사용법:
23
+ sh-ui theme extract tokens.css 의 색·radius 를 base64 로 추출 (stdout)
24
+
25
+ 옵션:
26
+ --out <path> 추출 결과를 stdout 대신 파일로 저장
27
+
28
+ 예:
29
+ sh-ui theme extract
30
+ sh-ui theme extract --out theme.txt
31
+ `;
32
+
20
33
  /**
21
34
  * Dart 의 ShUiColorTokens 필드명 ↔ TOKEN_KEYS (hyphen) 매핑.
22
35
  * inject.js 의 DART_FIELD_SOURCES 와 같은 매핑을 reverse 로 사용.
@@ -17,6 +17,18 @@ import { THEME_BASES } from "./constants.js";
17
17
  import { parseBlocks, diffBlocks, applyAdditions } from "./tokens-diff.mjs";
18
18
  import { parseDartTokens, diffDartTokens } from "./tokens-diff-dart.mjs";
19
19
 
20
+ export const HELP_TEXT = `sh-ui tokens — tokens.css 비교/업그레이드
21
+
22
+ 사용법:
23
+ sh-ui tokens diff tokens.css 와 buildTokens 결과 비교
24
+ sh-ui tokens upgrade --apply 추가된 변수만 적용 (사용자 편집 보존)
25
+ sh-ui tokens upgrade --replace buildTokens 결과로 통째 덮어쓰기
26
+
27
+ 예:
28
+ sh-ui tokens diff
29
+ sh-ui tokens upgrade --apply
30
+ `;
31
+
20
32
  async function loadTokensBuilder() {
21
33
  const url = pathToFileURL(resolve(getTokensRoot(), "build.mjs")).href;
22
34
  return import(url);
@@ -15,6 +15,19 @@ import { dirname, resolve } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { spawn } from "node:child_process";
17
17
 
18
+ export const HELP_TEXT = `sh-ui upgrade-cli — sh-ui-cli 자체를 최신으로 업그레이드
19
+
20
+ 사용법:
21
+ sh-ui upgrade-cli 최신 버전 확인 + 변경 내역 미리보기
22
+
23
+ 옵션:
24
+ --apply 실제 설치 후 진단 (미지정 시 미리보기만)
25
+
26
+ 예:
27
+ sh-ui upgrade-cli
28
+ sh-ui upgrade-cli --apply
29
+ `;
30
+
18
31
  const CLI_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
19
32
  const REGISTRY_URL = "https://registry.npmjs.org/sh-ui-cli/latest";
20
33