@conscience-technology/ctds 0.1.13 → 0.2.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.
Files changed (72) hide show
  1. package/assets/components/AppFrame/AppFrame.module.css +6 -0
  2. package/assets/components/AttachmentItem/AttachmentItem.module.css +112 -0
  3. package/assets/components/AttachmentItem/AttachmentItem.tsx +164 -0
  4. package/assets/components/AttachmentItem/index.ts +6 -0
  5. package/assets/components/Button/Button.module.css +11 -2
  6. package/assets/components/Button/Button.tsx +5 -5
  7. package/assets/components/ChatInput/ChatInput.tsx +2 -2
  8. package/assets/components/DatePicker/Calendar.tsx +19 -1
  9. package/assets/components/Drawer/Drawer.tsx +120 -39
  10. package/assets/components/Drawer/index.ts +9 -1
  11. package/assets/components/GeneratedImage/GeneratedImage.module.css +90 -0
  12. package/assets/components/GeneratedImage/GeneratedImage.tsx +70 -0
  13. package/assets/components/GeneratedImage/index.ts +5 -0
  14. package/assets/components/Icon/Icon.tsx +4 -1
  15. package/assets/components/Input/Input.module.css +3 -0
  16. package/assets/components/MessageState/MessageState.module.css +12 -6
  17. package/assets/components/MessageState/MessageState.tsx +23 -5
  18. package/assets/components/Modal/Modal.tsx +114 -33
  19. package/assets/components/Modal/index.ts +12 -2
  20. package/assets/components/Spinner/Spinner.module.css +1 -1
  21. package/assets/components/Spinner/Spinner.tsx +2 -2
  22. package/assets/components/Switch/Switch.tsx +6 -0
  23. package/assets/components/TableHeader/TableHeader.module.css +29 -20
  24. package/assets/components/TableRow/TableRow.module.css +7 -5
  25. package/assets/components/TableRowEdit/TableRowEdit.module.css +10 -3
  26. package/assets/components/TableRowEdit/TableRowEdit.tsx +7 -2
  27. package/assets/components/Toast/Toast.tsx +19 -0
  28. package/assets/components/_data.ts +39 -14
  29. package/assets/components/app-shell.css +17 -0
  30. package/assets/components/index.ts +77 -0
  31. package/assets/components/reset.css +63 -0
  32. package/assets/components/themeBootScript.ts +40 -0
  33. package/assets/components/tokens.css +70 -60
  34. package/assets/prompts/GUIDE.md +1 -1
  35. package/assets/prompts/PRINCIPLES.md +9 -0
  36. package/assets/prompts/axes/cascade.md +49 -0
  37. package/assets/prompts/axes/mode.md +3 -1
  38. package/assets/prompts/components/_variants.md +84 -0
  39. package/assets/prompts/components/attachment-item.md +80 -0
  40. package/assets/prompts/components/attachment-thumb.md +2 -2
  41. package/assets/prompts/components/avatar-upload.md +53 -0
  42. package/assets/prompts/components/bubble.md +2 -2
  43. package/assets/prompts/components/chat-input.md +7 -8
  44. package/assets/prompts/components/code-block.md +2 -2
  45. package/assets/prompts/components/disclosure.md +47 -0
  46. package/assets/prompts/components/file-icon.md +3 -3
  47. package/assets/prompts/components/generated-image.md +38 -0
  48. package/assets/prompts/components/message-quote.md +2 -2
  49. package/assets/prompts/components/message-sources.md +5 -4
  50. package/assets/prompts/components/message-state.md +15 -12
  51. package/assets/prompts/components/message-table.md +9 -6
  52. package/assets/prompts/components/onboarding.md +43 -0
  53. package/assets/prompts/components/quotation-bar.md +1 -1
  54. package/assets/prompts/components/sidebar-split.md +70 -0
  55. package/assets/prompts/components/sidebar.md +2 -0
  56. package/assets/prompts/components/spinner.md +4 -4
  57. package/assets/prompts/components/time-picker.md +69 -0
  58. package/assets/prompts/components/toolbar.md +69 -0
  59. package/assets/prompts/components/user-message.md +2 -2
  60. package/package.json +80 -3
  61. package/src/commands/init.js +53 -23
  62. package/src/utils/deps.js +23 -17
  63. package/src/utils/patch.js +155 -0
  64. package/assets/components/AttachmentChip/AttachmentChip.module.css +0 -73
  65. package/assets/components/AttachmentChip/AttachmentChip.tsx +0 -74
  66. package/assets/components/AttachmentChip/index.ts +0 -2
  67. package/assets/components/Calendar/Calendar.module.css +0 -163
  68. package/assets/components/Calendar/Calendar.tsx +0 -323
  69. package/assets/components/Calendar/index.ts +0 -2
  70. package/assets/prompts/components/attachment-chip.md +0 -46
  71. package/assets/prompts/components/calendar.md +0 -116
  72. package/assets/prompts/components/form-field.md +0 -74
@@ -21,7 +21,7 @@ ChatInput 상단·Message 안에서 "**다른 글을 인용**"을 1줄로 접어
21
21
  ### 관련
22
22
  - [[components/chat-input]] — 상위 소비처 (상단 bar 자리)
23
23
  - [[components/message-bubble]] — 메시지 내 인용 표시에서도 재사용
24
- - [[components/attachment-chip]] — 비슷한 레이아웃 (lead icon + content + X)
24
+ - [[components/attachment-item]] — 비슷한 레이아웃 (lead icon + content + X)
25
25
 
26
26
  ### Props 시그니처
27
27
  ```tsx
@@ -0,0 +1,70 @@
1
+ # SidebarSplit
2
+
3
+ ### 원칙
4
+ 2단 사이드바 — 좌측 좁은 **Rail**(아이콘 only 탭) + 우측 **Panel**(활성 탭의 상세 메뉴). VS Code·Linear 스타일 "활동 바 + 사이드 패널" 조합. 단일 폭 Sidebar가 메뉴 수를 다 담지 못할 때, 또는 완전히 다른 영역을 전환하는 네비게이션에서.
5
+
6
+ 메모리 주의: **"2단 쉘은 SidebarSplit 쓰지 말고 바깥 flex로 Rail+Sidebar 직접 조립"** ([Sidebar scroll trap](../../memory/feedback_sidebar_scroll.md)). 그 함정은 CtdsSidebar.Body 안 섹션이 뭉개지는 상황. SidebarSplit 자체는 panel 안 콘텐츠가 **menu/section 구조**일 때 안전하게 쓴다 — 복잡한 폼·검색 결과를 panel에 넣을 거면 Rail + Sidebar를 바깥 flex로 직접 조립.
7
+
8
+ ### Do
9
+ - 각 section의 `icon`은 Icon 인스턴스로. `label`은 rail 버튼의 `aria-label` + panel 헤더 타이틀로 재사용.
10
+ - panel 안은 `<SidebarSplit.Section title="…">` + `<SidebarSplit.Item icon label>` 조합을 기본으로. 섹션 하나에 Item 5~10개 수준이 적정.
11
+ - 좁은 공간이 필요하면 `panelOpen={false}` + onPanelOpenChange로 접기 토글. Rail만 남고 Panel은 숨음.
12
+ - 섹션이 **active일 때 Rail 아이콘은 `secondary` variant**로 자동 전환 — 사용자가 override하지 말 것.
13
+
14
+ ### Don't
15
+ - panel 안에 긴 폼·에디터를 넣지 않는다 — SidebarSplit은 "메뉴 전환" 자리. 콘텐츠는 메인 영역에.
16
+ - Rail 섹션을 **6개 이상** 쌓지 않는다. 세로 공간상 7~8개부터 하단이 잘림. 그 이상이면 메뉴 구조 재검토.
17
+ - `defaultActiveId`를 지정하지 않고 섹션 리스트를 조건부 렌더링하지 않는다 — 초기값이 비면 panel이 비어 보임.
18
+ - 2단 쉘 바깥(앱 전체) 레이아웃에 SidebarSplit을 **직접** 쓰지 않는다. 바깥 쉘은 flex로 Rail + CtdsSidebar를 수동 조립 — 메모리 참조.
19
+
20
+ ### Props 시그니처
21
+ ```tsx
22
+ <SidebarSplit
23
+ sections={SidebarSplitSection[]} // { id, icon, label, panel }[]
24
+ brand?={ReactNode} // rail 상단 로고
25
+ railFooter?={ReactNode}
26
+ panelFooter?={ReactNode}
27
+ activeId?={string} // controlled
28
+ defaultActiveId?={string}
29
+ onActiveChange?={(id: string) => void}
30
+ panelOpen?={boolean} // controlled
31
+ defaultPanelOpen?={boolean} // default: true
32
+ onPanelOpenChange?={(open: boolean) => void}
33
+ >
34
+ ```
35
+
36
+ Compound sub:
37
+ - `<SidebarSplit.Section title>` — panel 안 그룹 (role="group")
38
+ - `<SidebarSplit.Item icon label active trailing shortcut hasSubmenu onClick>` — MenuItem 래퍼
39
+
40
+ ### 조합 예시
41
+ ```tsx
42
+ const sections: SidebarSplitSection[] = [
43
+ {
44
+ id: "files",
45
+ icon: <Icon name="folder" />,
46
+ label: "파일",
47
+ panel: (
48
+ <>
49
+ <SidebarSplit.Section title="최근">
50
+ <SidebarSplit.Item icon={<Icon name="file" />} label="README.md" />
51
+ <SidebarSplit.Item icon={<Icon name="file" />} label="plan.md" />
52
+ </SidebarSplit.Section>
53
+ </>
54
+ ),
55
+ },
56
+ {
57
+ id: "search",
58
+ icon: <Icon name="search" />,
59
+ label: "검색",
60
+ panel: <Input placeholder="검색…" />,
61
+ },
62
+ ];
63
+
64
+ <SidebarSplit sections={sections} defaultActiveId="files" />
65
+ ```
66
+
67
+ ### 관련
68
+ - [[components/sidebar]] — 단일 폭 사이드바
69
+ - [[components/menu-item]] — panel item의 베이스
70
+ - [[components/icon-button]] — rail 버튼
@@ -33,6 +33,8 @@ Header는 **가로 행**이기 때문에 Section을 넣으면 nav 전체가 가
33
33
  - Sidebar 자체에 padding/width를 CSS로 덮지 않는다. `--sidebar-w*` 토큰 경유.
34
34
  - `.root`에 padding을 꽂지 않는다 — Body가 스크롤 컨테이너라 스크롤바가 안쪽으로 밀려 가장자리에 안 붙는다. 좌우 여백은 Header/Section/Footer가 각자 담당([[project_scroll_rule]]).
35
35
  - Brand symbol·wordmark 크기 override 금지. `--sidebar-brand-symbol-max`·`--sidebar-brand-wordmark-max` 상한 안에서만.
36
+ - **Body 안 섹션 리스트가 길어서 스크롤이 필요할 때**, 섹션들에 `flex-shrink`를 그대로 두지 않는다. Body는 `display:flex column`이라 자식(Section)이 기본 `flex-shrink:1`로 수축되어 header가 몇 px로 뭉개지고 아이템이 사라져 보인다. 해결: Body 직속 자식에 `flex: 0 0 auto`를 걸어 각 섹션이 자기 콘텐츠 전체 높이를 유지하게 하고, 스크롤은 Body의 `overflow-y:auto`가 담당하게 한다.
37
+ - **2단 쉘(아이콘 Rail + Sidebar) 조립 시 `SidebarSplit`를 쓰지 않는다** — SidebarSplit는 panel 영역을 자체 flex 체인으로 제한해서 안쪽 콘텐츠가 "영역 안에 갇힌" 느낌이 된다. 그냥 바깥 `display:flex`로 Rail(IconButton 세로 스택)과 `<Sidebar>`를 가로로 조립하고, 스크롤은 CtdsSidebar.Body에게 맡겨라. 기존 사이드바가 이미 자연 스크롤로 설계됐으니 그 구조를 그대로 차용하는 게 정답.
36
38
 
37
39
  ### Fallback
38
40
  collapsed 결정 애매하면 `false`. Section title 애매하면 생략. 경계선 애매하면 AppFrame `variant="outlined"`.
@@ -62,14 +62,14 @@
62
62
  **사이즈 주의**: Lottie는 vectorized라 size가 작으면 디테일 뭉개짐. `size="md"` 이상 권장. Button loading 안에 Lottie 쓰는 건 비권장 — 기본 variant(dots/ring)가 적합.
63
63
 
64
64
  ### Size 매트릭스
65
- IconButton 체계와 1:1 매칭. 로더는 캔버스를 다 채우지 않는 특성상 아이콘보다 한 단계 큰 bounds가 체감에 맞음.
65
+ IconButton 박스 크기와 1:1 매칭 — 로더가 들어가는 자리의 IconButton·Button 박스와 같은 size로.
66
66
 
67
67
  | size | 실제 크기 | 쓰임 |
68
68
  |---|---|---|
69
- | xs | 16px | Button xs 내부, 조밀한 인라인 |
70
- | sm | 20px | **기본**. 인라인 라벨 옆, 리스트 아이템, AIMessage generating |
69
+ | sm | 20px | 인라인 라벨 옆, 리스트 아이템, AIMessage generating |
71
70
  | md | 24px | 섹션 로딩, 카드 중앙 |
72
- | lg | 32px | 페이지 중앙 로딩, 모달 바디 로딩 |
71
+ | lg | 32px | **기본**. 조금 더 강조된 영역 |
72
+ | xl | 40px | 페이지 중앙 로딩, 모달 바디 로딩, Button loading(전 사이즈) |
73
73
 
74
74
  Lottie 로더는 캔버스 충전율이 다르므로 `visualScale`로 bounds를 꽉 채우도록 보정됨 — size만 고르면 체감이 일관되게 맞아야 함. 특정 Lottie가 작아 보이면 `lottieManifest.ts`의 `visualScale`을 올려 글로벌 보정, 사용처에서 size를 올리지 말 것.
75
75
 
@@ -0,0 +1,69 @@
1
+ # TimePicker
2
+
3
+ ### 원칙
4
+ 시·분 선택 입력. [[components/date-picker]]의 시간 짝. 트리거는 세그먼트 입력(HH:MM 2자리씩) + popover 안에 시/분 리스트 또는 전체 슬롯 그리드. "약속 시간·회의 시작·마감 시각" 자리.
5
+
6
+ ### Do
7
+ - `variant="line"` 기본. 폼 블록이면 `outline`, 밀도 극한이면 `filled` ([Input line default](../../memory/feedback_input_line_default.md) 메모리와 동일 규칙).
8
+ - `step={30}` 기본. 회의·일정은 30분 단위가 흔함. 5·10·15·60도 선택 가능하지만 그 외 값은 자동 clamp.
9
+ - 24h 지역이면 `format="24h"`(기본), 12h 지역이면 `format="12h"` + meridiem 세그먼트.
10
+ - `quickOptions`는 "지금부터 상대 시간"이 흔한 자리(알람·예약 리마인더)에서만 켠다. 일반 폼엔 노이즈.
11
+ - 예약 가능 슬롯을 그리드로 보여줘야 하는 자리(병원·식당 예약)엔 `inline + panelLayout="grid"`. gridCols 4~8 중 레이아웃 폭에 맞춰.
12
+
13
+ ### Don't
14
+ - step에 7·13·45 같은 비표준 분수치를 넣지 않는다 — 리스트 가독성이 깨짐. 5·10·15·30·60만.
15
+ - `minTime`/`maxTime`은 "HH:MM" 문자열만. Date 객체·epoch 금지.
16
+ - `panelLayout="grid"`를 popover 모드(`inline=false`)에서 쓰지 않는다 — 그리드는 inline 전용.
17
+ - Input처럼 생겼다고 `<Input type="time">`을 섞어쓰지 않는다 — 네이티브 time input은 축·토큰이 적용 안 됨.
18
+
19
+ ### Props 시그니처
20
+ ```tsx
21
+ <TimePicker
22
+ value?={TimeValue | null} // { hours, minutes }
23
+ onChange?={(v: TimeValue | null) => void}
24
+ step?={5 | 10 | 15 | 30 | 60} // default: 30
25
+ format?="24h | 12h" // default: 24h
26
+ minTime?={string} // "HH:MM" (24h, inclusive)
27
+ maxTime?={string} // "HH:MM" (24h, inclusive)
28
+ disabled?={boolean}
29
+ size?="sm | md | lg" // default: md
30
+ variant?="outline | filled | line" // default: outline
31
+ inline?={boolean} // popover 없이 패널만
32
+ panelLayout?="columns | grid" // default: columns
33
+ gridCols?={4 | 6 | 8} // grid 모드 열 수
34
+ splitMeridiem?={boolean} // grid를 AM/PM 2섹션
35
+ quickOptions?={QuickOption[] | boolean}
36
+ aria-label?={string}
37
+ />
38
+ ```
39
+
40
+ ### 조합 예시
41
+ ```tsx
42
+ // 폼 — 시작·종료 시간
43
+ <TimePicker variant="outline" step={15} value={start} onChange={setStart} />
44
+ <TimePicker variant="outline" step={15} value={end} onChange={setEnd} minTime="09:00" />
45
+
46
+ // 예약 슬롯 그리드 (오전·오후 분리)
47
+ <TimePicker
48
+ inline
49
+ panelLayout="grid"
50
+ step={30}
51
+ splitMeridiem
52
+ gridCols={6}
53
+ value={slot}
54
+ onChange={setSlot}
55
+ />
56
+
57
+ // 알람 — 지금부터 15/30/60분 후
58
+ <TimePicker
59
+ variant="line"
60
+ quickOptions
61
+ value={remind}
62
+ onChange={setRemind}
63
+ />
64
+ ```
65
+
66
+ ### 관련
67
+ - [[components/date-picker]] — 날짜 짝. 날짜+시간이 함께면 둘을 나란히.
68
+ - [[components/input]] — variant 규칙 동일
69
+ - [[components/select]] — 밀도 극한에서 Select로 대체 가능
@@ -0,0 +1,69 @@
1
+ # Toolbar
2
+
3
+ ### 원칙
4
+ IconButton 중심의 action 줄. 에디터·캔버스·갤러리 상단, 또는 "뜨 있는" 알약으로 콘텐츠 위에 떠서 액션을 모으는 자리. 단일 size — toolbar 안의 버튼은 전부 같은 사이즈여야 한다.
5
+
6
+ ### Do
7
+ - `variant="floating"` 기본 — bg-elevated + shadow-md + radius-xl, 콘텐츠 위에 떠있는 알약. 캔버스·에디터·이미지 뷰어의 상단 중앙.
8
+ - `variant="attached"`는 콘텐츠에 붙은 bar — transparent + 하단 hairline. 페이지 상단에 "탭 아래 액션 바"로 쓸 때.
9
+ - 관련 액션은 `<Toolbar.Group>`으로 묶는다 — 내부는 좁은 gap, 그룹 간은 `<Toolbar.Divider>`.
10
+ - 좌·우 정렬이 필요하면 `<Toolbar.Spacer>`로 공간 밀기 — flex:1.
11
+ - toolbar 안엔 IconButton size="sm"이 기본. floating variant는 size="md"까지도 OK.
12
+
13
+ ### Don't
14
+ - toolbar 안에 label 긴 Button을 섞지 않는다 — IconButton + Tooltip이 toolbar 패턴. 긴 label이 필요하면 그 자리는 toolbar가 아니라 액션 영역.
15
+ - 두 Toolbar를 세로로 쌓지 않는다 — 하나의 toolbar에 Group·Divider로 구분. 쌓으면 밀도·시선 둘 다 깨짐.
16
+ - `attached` variant에 shadow·radius-xl를 수동으로 추가하지 않는다 — floating을 쓰면 됨.
17
+ - Group 안에 또 Group을 중첩하지 않는다 — flat 2단(Toolbar > Group > IconButton).
18
+
19
+ ### Props 시그니처
20
+ ```tsx
21
+ <Toolbar
22
+ variant?="floating | attached" // default: floating
23
+ stretch?={boolean} // default: variant로 자동 (attached=true, floating=false)
24
+ role?={string} // default: "toolbar"
25
+ >
26
+ {children}
27
+ </Toolbar>
28
+ ```
29
+
30
+ Compound sub:
31
+ - `<Toolbar.Group>` — 관련 액션 묶음 (role="group")
32
+ - `<Toolbar.Divider>` — 세로 hairline
33
+ - `<Toolbar.Spacer>` — flex:1 공간 밀기
34
+
35
+ ### 조합 예시
36
+ ```tsx
37
+ // 에디터 — floating 알약, 좌/중/우 섹션
38
+ <Toolbar variant="floating">
39
+ <Toolbar.Group>
40
+ <IconButton size="sm" variant="ghost" aria-label="실행 취소" icon={<Icon name="arrow-u-up-left" />} />
41
+ <IconButton size="sm" variant="ghost" aria-label="다시 실행" icon={<Icon name="arrow-u-up-right" />} />
42
+ </Toolbar.Group>
43
+ <Toolbar.Divider />
44
+ <Toolbar.Group>
45
+ <IconButton size="sm" variant="ghost" aria-label="굵게" icon={<Icon name="text-b" />} />
46
+ <IconButton size="sm" variant="ghost" aria-label="기울임" icon={<Icon name="text-italic" />} />
47
+ </Toolbar.Group>
48
+ <Toolbar.Spacer />
49
+ <Toolbar.Group>
50
+ <IconButton size="sm" variant="ghost" aria-label="공유" icon={<Icon name="share" />} />
51
+ </Toolbar.Group>
52
+ </Toolbar>
53
+
54
+ // 페이지 상단 — attached, 콘텐츠에 붙음
55
+ <Toolbar variant="attached">
56
+ <Toolbar.Group>
57
+ <Button size="sm" variant="ghost" leftIcon={<Icon name="plus" />}>새로 만들기</Button>
58
+ <Button size="sm" variant="ghost" leftIcon={<Icon name="funnel" />}>필터</Button>
59
+ </Toolbar.Group>
60
+ <Toolbar.Spacer />
61
+ <IconButton size="sm" variant="ghost" aria-label="보기 전환" icon={<Icon name="list" />} />
62
+ </Toolbar>
63
+ ```
64
+
65
+ ### 관련
66
+ - [[components/icon-button]] — toolbar 안 주 버튼
67
+ - [[components/button]] — label 짧은 액션
68
+ - [[components/tooltip]] — IconButton엔 Tooltip 필수
69
+ - [[components/breadcrumb]] — 페이지 상단에선 Breadcrumb + Toolbar 조합이 흔함
@@ -6,7 +6,7 @@
6
6
  ### Do
7
7
  - 기본 구성: `shape=circular-drop, fill=translucent, tail=end` — 레퍼런스 톤.
8
8
  - 액션은 `[copy, edit]` 기본. `actionsVisibility="show" | "hover"`로 상시/호버.
9
- - 첨부 있으면 `before` slot에 [[components/attachment-thumb]] / [[components/attachment-chip]] 자유 조합. 상위에서 레이아웃 책임.
9
+ - 첨부 있으면 `before` slot에 [[components/attachment-thumb]] / [[components/attachment-item]] 자유 조합. 상위에서 레이아웃 책임.
10
10
  - 매우 긴 글은 bubble이 max-width 안에서 wrap, 그보다 넘으면 자연스레 아래로.
11
11
 
12
12
  ### Don't
@@ -22,7 +22,7 @@ shape 애매하면 `circular-drop`, fill 애매하면 `translucent`, visibility
22
22
  - [[components/bubble]] — 담는 그릇
23
23
  - [[components/message-actions]] — 액션 바
24
24
  - [[components/ai-message]] — 짝 컴포넌트
25
- - [[components/attachment-thumb]] / [[components/attachment-chip]] — before slot 조합
25
+ - [[components/attachment-thumb]] / [[components/attachment-item]] — before slot 조합
26
26
 
27
27
  ### Props 시그니처
28
28
  ```tsx
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@conscience-technology/ctds",
3
- "version": "0.1.13",
4
- "description": "CTDS — 하이브리드 디자인 시스템 설치 · 온보딩 CLI",
3
+ "version": "0.2.0",
4
+ "description": "CTDS — 하이브리드 디자인 시스템 설치 · 온보딩 CLI (shadcn식 코드 생성기)",
5
5
  "type": "module",
6
+ "sideEffects": [
7
+ "assets/components/**/*.css",
8
+ "assets/app/**/*.css"
9
+ ],
6
10
  "bin": {
7
11
  "ctds": "./bin/ctds.js"
8
12
  },
@@ -13,6 +17,13 @@
13
17
  "templates",
14
18
  "README.md"
15
19
  ],
20
+ "exports": {
21
+ ".": {
22
+ "import": "./bin/ctds.js"
23
+ },
24
+ "./assets/*": "./assets/*",
25
+ "./templates/*": "./templates/*"
26
+ },
16
27
  "scripts": {
17
28
  "bundle-assets": "node ./scripts/bundle-assets.mjs",
18
29
  "prepublishOnly": "npm run bundle-assets"
@@ -23,7 +34,11 @@
23
34
  "keywords": [
24
35
  "ctds",
25
36
  "design-system",
26
- "cli"
37
+ "cli",
38
+ "shadcn",
39
+ "nextjs",
40
+ "react",
41
+ "tokens"
27
42
  ],
28
43
  "license": "MIT",
29
44
  "publishConfig": {
@@ -33,5 +48,67 @@
33
48
  "type": "git",
34
49
  "url": "git+https://github.com/Conscience-Technology/CTDS.git",
35
50
  "directory": "packages/ctds-cli"
51
+ },
52
+ "peerDependencies": {
53
+ "react": ">=18.2.0",
54
+ "react-dom": ">=18.2.0",
55
+ "next": ">=14.0.0",
56
+ "@phosphor-icons/react": "^2.1.10",
57
+ "solar-icon-set": "^2.0.1",
58
+ "lucide-react": "^1.47.0",
59
+ "@radix-ui/react-popover": "^1.1.23",
60
+ "@radix-ui/react-select": "^2.3.7",
61
+ "@radix-ui/react-tooltip": "^1.2.16",
62
+ "@radix-ui/react-context-menu": "^2.3.7",
63
+ "@radix-ui/react-dialog": "^1.2.0",
64
+ "@radix-ui/react-toast": "^1.2.24",
65
+ "@radix-ui/react-slot": "^1.3.3",
66
+ "@dnd-kit/core": "^6.3.1",
67
+ "@dnd-kit/sortable": "^10.0.0",
68
+ "@dnd-kit/utilities": "^3.2.2"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "next": {
72
+ "optional": true
73
+ },
74
+ "@phosphor-icons/react": {
75
+ "optional": true
76
+ },
77
+ "solar-icon-set": {
78
+ "optional": true
79
+ },
80
+ "lucide-react": {
81
+ "optional": true
82
+ },
83
+ "@radix-ui/react-popover": {
84
+ "optional": true
85
+ },
86
+ "@radix-ui/react-select": {
87
+ "optional": true
88
+ },
89
+ "@radix-ui/react-tooltip": {
90
+ "optional": true
91
+ },
92
+ "@radix-ui/react-context-menu": {
93
+ "optional": true
94
+ },
95
+ "@radix-ui/react-dialog": {
96
+ "optional": true
97
+ },
98
+ "@radix-ui/react-toast": {
99
+ "optional": true
100
+ },
101
+ "@radix-ui/react-slot": {
102
+ "optional": true
103
+ },
104
+ "@dnd-kit/core": {
105
+ "optional": true
106
+ },
107
+ "@dnd-kit/sortable": {
108
+ "optional": true
109
+ },
110
+ "@dnd-kit/utilities": {
111
+ "optional": true
112
+ }
36
113
  }
37
114
  }
@@ -23,6 +23,7 @@ import { checkMissingDeps, renderInstallCmd } from "../utils/deps.js";
23
23
  import { c, step, success, warn, info, kv } from "../utils/log.js";
24
24
  import { computeFilesMap, writeMeta } from "../utils/meta.js";
25
25
  import { hasDevScript, startDevAndOpen } from "../utils/launch.js";
26
+ import { patchGlobalsCss, patchLayoutTsx } from "../utils/patch.js";
26
27
 
27
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
28
29
  const CLI_PKG_PATH = join(__dirname, "..", "..", "package.json");
@@ -126,45 +127,74 @@ export async function run(args) {
126
127
  }
127
128
  }
128
129
 
129
- // 3) 수동 안내 — layout.tsx wrapper + globals.css import + AI 에이전트 안내
130
+ // 3) 자동 패치 — globals.css import + layout.tsx ThemeController wrapper
130
131
  console.log("");
131
132
  console.log(c.bold("설치 완료"));
132
133
  console.log(c.dim("—").repeat(40));
133
134
  success("자동으로 복사·생성된 파일은 위 리스트와 같습니다.");
134
135
  console.log("");
135
136
 
136
- console.log(c.bold("다음 두 가지를 수동으로 설정해주세요:"));
137
- console.log("");
137
+ const manualFallbacks = [];
138
138
 
139
139
  // 3-1) globals.css — tokens.css import (폰트 + CTDS 토큰 + 축 셀렉터 전부 포함)
140
140
  const globalsRel = project.useSrcDir ? "src/app/globals.css" : "app/globals.css";
141
- info(`${c.bold("1)")} ${c.cyan(globalsRel)} 최상단에 다음 한 줄을 추가:`);
142
- console.log(c.dim("```css"));
143
- console.log(`@import "../components/ctds/tokens.css";`);
144
- console.log(c.dim("```"));
145
- console.log(c.dim(" tokens.css가 폰트 로드 + CTDS 토큰 + Palette/Shape/Density/Motion/Mode"));
146
- console.log(c.dim(" 축 셀렉터까지 전부 담당합니다. 이 import 하나면 CTDS 작동 OK."));
147
- console.log("");
141
+ const globalsAbs = resolve(cwd, globalsRel);
142
+ const globalsResult = patchGlobalsCss(globalsAbs, { dry });
143
+ if (globalsResult.status === "applied") {
144
+ step(dry ? `[dry] 패치` : `패치`, `${globalsRel} — @import 추가`);
145
+ } else if (globalsResult.status === "skipped") {
146
+ info(`${globalsRel} — ${globalsResult.reason}`);
147
+ } else {
148
+ warn(`${globalsRel} 자동 패치 실패: ${globalsResult.reason}`);
149
+ manualFallbacks.push({
150
+ title: `${c.cyan(globalsRel)} 최상단에 다음 한 줄을 추가:`,
151
+ lang: "css",
152
+ body: `@import "../components/ctds/tokens.css";`,
153
+ note: "tokens.css가 폰트 로드 + CTDS 토큰 + 축 셀렉터 전부 담당합니다.",
154
+ });
155
+ }
148
156
 
149
157
  // 3-2) layout.tsx wrapper
150
158
  const layoutRel = project.useSrcDir ? "src/app/layout.tsx" : "app/layout.tsx";
151
- info(`${c.bold("2)")} ${c.cyan(layoutRel)} 의 <html>·<body>를 아래 형태로 수정:`);
152
- console.log(c.dim("```tsx"));
153
- console.log(renderLayoutSnippet(project));
154
- console.log(c.dim("```"));
155
- console.log("");
159
+ const layoutAbs = resolve(cwd, layoutRel);
160
+ const layoutResult = patchLayoutTsx(layoutAbs, { dry });
161
+ if (layoutResult.status === "applied") {
162
+ step(dry ? `[dry] 패치` : `패치`, `${layoutRel} — <body>를 ThemeController로 감쌈`);
163
+ } else if (layoutResult.status === "skipped") {
164
+ info(`${layoutRel} — ${layoutResult.reason}`);
165
+ } else {
166
+ warn(`${layoutRel} 자동 패치 실패: ${layoutResult.reason}`);
167
+ manualFallbacks.push({
168
+ title: `${c.cyan(layoutRel)} 의 <html>·<body>를 아래 형태로 수정:`,
169
+ lang: "tsx",
170
+ body: renderLayoutSnippet(project),
171
+ });
172
+ }
156
173
 
157
174
  // 3-3) 의존성 체크
158
175
  const { missing } = checkMissingDeps(cwd);
159
176
  if (missing.length > 0) {
160
- info(`${c.bold("3)")} 다음 패키지를 설치해주세요 (CTDS 컴포넌트가 필요):`);
161
- console.log(c.dim("```bash"));
162
- console.log(renderInstallCmd(missing));
163
- console.log(c.dim("```"));
164
- console.log("");
177
+ manualFallbacks.push({
178
+ title: "다음 패키지를 설치해주세요 (CTDS 컴포넌트가 필요):",
179
+ lang: "bash",
180
+ body: renderInstallCmd(missing),
181
+ });
165
182
  } else {
166
183
  success("필요한 의존성이 모두 설치돼 있어요.");
184
+ }
185
+
186
+ if (manualFallbacks.length > 0) {
187
+ console.log("");
188
+ console.log(c.bold("수동 설정이 남았어요:"));
167
189
  console.log("");
190
+ manualFallbacks.forEach((m, idx) => {
191
+ info(`${c.bold(`${idx + 1})`)} ${m.title}`);
192
+ console.log(c.dim("```" + m.lang));
193
+ console.log(m.body);
194
+ console.log(c.dim("```"));
195
+ if (m.note) console.log(c.dim(" " + m.note));
196
+ console.log("");
197
+ });
168
198
  }
169
199
 
170
200
  // 4) 다음 단계
@@ -172,9 +202,9 @@ export async function run(args) {
172
202
  console.log(c.dim("—").repeat(40));
173
203
  console.log("");
174
204
 
175
- // 수동 설정이 남은 경우(globals.css import / layout.tsx wrapper / 의존성) 자동 실행 보류.
176
- // 서버가 안 뜨거나 뜨더라도 화면이 깨져서 혼란만 커짐.
177
- const needsManual = missing.length > 0;
205
+ // 수동 설정이 남은 경우(globals.css import 자동 패치 실패 / layout.tsx wrapper 실패 /
206
+ // 의존성 설치 필요) 자동 실행 보류. 서버가 안 뜨거나 뜨더라도 화면이 깨져서 혼란만 커짐.
207
+ const needsManual = manualFallbacks.length > 0;
178
208
 
179
209
  if (dry || noOpen || needsManual || !hasDevScript(cwd)) {
180
210
  console.log(` ${c.cyan("npm run dev")}`);
package/src/utils/deps.js CHANGED
@@ -1,27 +1,33 @@
1
1
  /**
2
2
  * CTDS 런타임 의존성 체크 + 설치 명령 생성.
3
3
  * 사용자 플젝 package.json을 읽어 빠진 패키지만 안내.
4
+ *
5
+ * Single source of truth: ctds-cli/package.json의 peerDependencies.
6
+ * react/react-dom/next는 소비자가 당연히 가진다고 가정하고 체크에서 제외.
4
7
  */
5
8
 
6
9
  import { existsSync, readFileSync } from "node:fs";
7
- import { join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { dirname, join, resolve } from "node:path";
8
12
 
9
- /** CTDS 컴포넌트가 실제로 import하는 외부 패키지. */
10
- export const CTDS_RUNTIME_DEPS = {
11
- "@phosphor-icons/react": "^2.1.10",
12
- "solar-icon-set": "^2.0.1",
13
- "lucide-react": "^1.47.0",
14
- "@radix-ui/react-popover": "^1.1.23",
15
- "@radix-ui/react-select": "^2.3.7",
16
- "@radix-ui/react-tooltip": "^1.2.16",
17
- "@radix-ui/react-context-menu": "^2.3.7",
18
- "@radix-ui/react-dialog": "^1.2.0",
19
- "@radix-ui/react-toast": "^1.2.24",
20
- "@radix-ui/react-slot": "^1.3.3",
21
- "@dnd-kit/core": "^6.3.1",
22
- "@dnd-kit/sortable": "^10.0.0",
23
- "@dnd-kit/utilities": "^3.2.2",
24
- };
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const CLI_PKG_PATH = resolve(__dirname, "../../package.json");
16
+
17
+ /** 소비자 레포가 당연히 가진 호스트 환경 패키지. install 안내 대상에서 제외. */
18
+ const HOST_DEPS = new Set(["react", "react-dom", "next"]);
19
+
20
+ /** CTDS 컴포넌트가 실제로 import하는 외부 패키지. CLI 자체 package.json에서 읽어온다. */
21
+ export const CTDS_RUNTIME_DEPS = (() => {
22
+ const pkg = JSON.parse(readFileSync(CLI_PKG_PATH, "utf8"));
23
+ const peers = pkg.peerDependencies ?? {};
24
+ const runtime = {};
25
+ for (const [name, range] of Object.entries(peers)) {
26
+ if (HOST_DEPS.has(name)) continue;
27
+ runtime[name] = range;
28
+ }
29
+ return runtime;
30
+ })();
25
31
 
26
32
  /**
27
33
  * 사용자 플젝의 package.json을 읽어 CTDS가 필요로 하는 deps 중 빠진 것 리스트.