oh-my-design-cli 1.6.1 → 1.6.2

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 (40) hide show
  1. package/README.ko.md +12 -0
  2. package/README.md +12 -0
  3. package/data/reference-fingerprints.json +979 -402
  4. package/dist/bin/oh-my-design.js +4 -3
  5. package/dist/bin/oh-my-design.js.map +1 -1
  6. package/dist/{install-skills-UKEVE3KT.js → install-skills-6QFSN5BN.js} +98 -34
  7. package/dist/install-skills-6QFSN5BN.js.map +1 -0
  8. package/package.json +2 -1
  9. package/skills/claude-design/SKILL.md +385 -0
  10. package/skills/claude-design/references/claude-design-flow.md +425 -0
  11. package/skills/claude-design/references/codebase-analysis.md +373 -0
  12. package/skills/claude-design/scripts/analyze_codebase.py +1369 -0
  13. package/skills/claude-design/scripts/clickable_link.sh +48 -0
  14. package/skills/claude-design/scripts/collect_source.py +178 -0
  15. package/skills/claude-design/scripts/drive_claude_design.cjs +378 -0
  16. package/skills/claude-design/scripts/gather_references.py +437 -0
  17. package/web/references/bunjang/DESIGN.md +1 -1
  18. package/web/references/classting/DESIGN.md +251 -0
  19. package/web/references/coinone/DESIGN.md +218 -0
  20. package/web/references/devsisters/DESIGN.md +253 -0
  21. package/web/references/drnow/DESIGN.md +331 -0
  22. package/web/references/flo/DESIGN.md +306 -0
  23. package/web/references/fugle/DESIGN.md +250 -0
  24. package/web/references/gogolook/DESIGN.md +5 -0
  25. package/web/references/grip/DESIGN.md +250 -0
  26. package/web/references/hogangnono/DESIGN.md +308 -0
  27. package/web/references/hyundaicard/DESIGN.md +5 -0
  28. package/web/references/jkopay/DESIGN.md +249 -0
  29. package/web/references/jobkorea/DESIGN.md +310 -0
  30. package/web/references/krafton/DESIGN.md +230 -0
  31. package/web/references/laftel/DESIGN.md +253 -0
  32. package/web/references/lezhin/DESIGN.md +301 -0
  33. package/web/references/momoshop/DESIGN.md +279 -0
  34. package/web/references/mustit/DESIGN.md +282 -0
  35. package/web/references/payco/DESIGN.md +227 -0
  36. package/web/references/piccollage/DESIGN.md +277 -0
  37. package/web/references/riiid/DESIGN.md +228 -0
  38. package/web/references/trenbe/DESIGN.md +252 -0
  39. package/web/references/voicetube/DESIGN.md +227 -0
  40. package/dist/install-skills-UKEVE3KT.js.map +0 -1
@@ -0,0 +1,252 @@
1
+ ---
2
+ id: trenbe
3
+ name: "Trenbe"
4
+ country: KR
5
+ category: ecommerce
6
+ homepage: "https://www.trenbe.com"
7
+ primary_color: "#7620F6"
8
+ logo:
9
+ type: favicon
10
+ slug: "https://www.google.com/s2/favicons?domain=trenbe.com&sz=256"
11
+ verified: "2026-06-03"
12
+ omd: "0.1"
13
+ ---
14
+
15
+ # Trenbe
16
+
17
+ Global No.1 luxury shopping platform from Korea, combining tech-driven authentication and real-time global price scanning with a purple-accented monochrome design language.
18
+
19
+ ## 1. Visual Theme & Atmosphere
20
+
21
+ Trenbe's interface balances the accessibility of mass-market ecommerce with the calm restraint expected of a luxury goods destination. The canvas is predominantly white with a warm near-black text system and deliberate use of Trenbe's signature vivid purple (#7620F6) as the sole accent color, signaling trust, exclusivity, and forward-facing technology. Product photography is given maximum breathing room within a tight typographic grid; decorative ornamentation is avoided in favor of clean rule lines and generous whitespace. On marketing surfaces the purple can appear in gradients and saturated CTAs, while within the shopping UI it is reserved for interactive states, labels, and primary links — preventing visual noise against high-resolution luxury imagery. The result feels simultaneously confident and approachable: global in aspiration, Korean in precision.
22
+
23
+ ## 2. Color Palette & Roles
24
+
25
+ - **Purple (Primary):** `#7620F6` — brand primary; maps to `--primary` and `--purple` across all Trenbe sub-domains; used for interactive labels, uiPrimary text, hover states on marketing CTAs
26
+ - **Purple 500 (UI Primary):** `#7351EC` — semantic `uiPrimary` / `textPrimary` token in app DS v4; active focus rings, highlight text
27
+ - **Black:** `#000000` — primary action button background (btn-primary, btn-black); high-emphasis foreground
28
+ - **White:** `#FFFFFF` — page canvas, card backgrounds, field01 input surface; theme-color meta
29
+ - **Gray 1000 (text01):** `#2F2E2B` — body text, headings, highest-contrast foreground
30
+ - **Gray 900 (text02):** `#4F4E4B` — secondary body text, descriptions
31
+ - **Gray 800 (text03):** `#6F6E6B` — tertiary text, subheadings
32
+ - **Gray 400 (border):** `#CFCECB` — hairline dividers, card borders
33
+ - **Gray 100 (surface):** `#F7F6F5` — page background, input field02 fill
34
+ - **Red 500 (Secondary):** `#EC5151` — semantic `uiSecondary` / `textSecondary`; sale badges, error states, price emphasis
35
+ - **Green 500 (Success):** `#1EB789` — success states, confirmation labels
36
+ - **Yellow 500 (Caution):** `#FFAB1E` — caution / promotional badges
37
+
38
+ ## 3. Typography Rules
39
+
40
+ - **Primary font:** Pretendard (weights 100–900 loaded via subset WOFF2), fallback: "Apple SD Gothic Neo", "Helvetica Neue", sans-serif
41
+ - **Feature settings:** `"ss03"`, `"cv01"` enabled on mobile — applied via `@media (max-width: 768px)` on `#app`
42
+ - **Type scale (v4 rem-based, base 10px root):**
43
+ - overline: 1.0rem / line-height 1.8rem
44
+ - caption: 1.2rem / 2.0rem
45
+ - body: 1.4rem / 2.2rem
46
+ - headline: 1.6rem / 2.4rem
47
+ - title: 1.8rem / 2.6rem
48
+ - display03: 2.0rem / 2.8rem
49
+ - display02: 2.4rem / 3.2rem
50
+ - display01: 3.2rem / 4.2rem
51
+ - **Legacy CSS (global fallback):** body font-size 13px, line-height 1.2, color `#444444`
52
+ - **Weight usage:** 400 for body, 500 for subheadings, 700 for CTAs and prices, 600 for section titles
53
+
54
+ ## 4. Component Stylings
55
+
56
+ ### Buttons
57
+
58
+ **Primary (Black CTA)**
59
+ - Background: `#000000`
60
+ - Text: `#FFFFFF`
61
+ - Border: 1px solid `#000000`
62
+ - Radius: 2px
63
+ - Padding: 6px 12px
64
+ - Font: 13px / 400
65
+
66
+ **Default (Ghost)**
67
+ - Background: `#FFFFFF`
68
+ - Text: `#000000`
69
+ - Border: 1px solid `#88888E`
70
+ - Radius: 2px
71
+ - Padding: 6px 12px
72
+ - Font: 13px / 400
73
+
74
+ **Disabled**
75
+ - Background: `#FFFFFF`
76
+ - Text: `#AFAEAB`
77
+ - Border: 1px solid `#CFCECB`
78
+ - Radius: 2px
79
+
80
+ **Marketing CTA (Brand Purple)**
81
+ - Background: `#7618F1`
82
+ - Text: `#FFFFFF`
83
+ - Border: none
84
+ - Radius: 4px
85
+ - Height: 55px
86
+ - Font: 18px / 600
87
+
88
+ ### Form Fields
89
+
90
+ **Default Input**
91
+ - Background: `#FFFFFF`
92
+ - Text: `#555555`
93
+ - Border: 1px solid `#CCCCCC`
94
+ - Radius: 0px
95
+ - Height: 29px
96
+ - Padding: 6px 12px
97
+ - Font: 13px / 400
98
+
99
+ **Focus Input**
100
+ - Background: `#FFFFFF`
101
+ - Text: `#555555`
102
+ - Border: 1px solid `#66AFE9`
103
+
104
+ ### Badges / Labels
105
+
106
+ **Primary Badge (Black)**
107
+ - Background: `#000000`
108
+ - Text: `#FFFFFF`
109
+ - Radius: 0px
110
+ - Padding: 0.2em 0.6em 0.3em
111
+
112
+ **Sale / Accent Badge (Red)**
113
+ - Background: `#EC5151`
114
+ - Text: `#FFFFFF`
115
+ - Radius: 4px
116
+
117
+ ---
118
+ **Verified:** 2026-06-03
119
+ **Tier 1 sources:** https://www.trenbe.com (HTML + theme-color meta), https://assets.trenbe.com/20260602.151834.7400ea3/main.696cafc00c26f4e3cdcd.js (embedded CSS + design token object), https://www.trenbecorp.com (corp CSS: common.css + main.css), https://trenbe.github.io (tech blog CSS: --primary: #7620F6), https://assets.trenbe.com/font/pretendard/pretendard-subset.css (confirmed Pretendard font)
120
+ **Tier 2 sources:** getdesign.md/trenbe — NOT LISTED (no data). refero — no result for Trenbe.
121
+ **Conflicts unresolved:** Two purple primary values co-exist in the codebase: #7620F6 (older token, used on tech blog and trenbecorp.com as `--primary`) and #7351EC (newer v4 DS token `purple500` / `uiPrimary`). The tech blog and corporate site both use #7620F6 as `--primary`, which is treated as the canonical brand primary here. The v4 DS purple500 (#7351EC) is the current interactive UI token in the product app.
122
+
123
+ ## 5. Layout Principles
124
+
125
+ - Single-column mobile-first grid; horizontal padding: 20px mobile, 41px tablet, 48px desktop
126
+ - Vertical rhythm follows spacing token scale (0.2rem–6.4rem in 8-step increments)
127
+ - Product grid: 2-column on mobile, 3–4 column on tablet/desktop, with consistent gutter matching horizontal padding
128
+ - Maximum content width: 1200px (pc breakpoint), centered with auto margins
129
+ - Cards have 8px radius (main borderRadius token), hairline 1px `#CFCECB` borders
130
+ - zIndex layers: header 1399, modal 1500, ProductCard 1
131
+
132
+ ## 6. Depth & Elevation
133
+
134
+ - Minimal shadow philosophy: UI avoids drop-shadows on cards; separation achieved via border `#CFCECB` and background-color contrast (`#F7F6F5` vs `#FFFFFF`)
135
+ - Modals use `rgba(0,0,0,0.5)` scrim backdrop (alpha50 token = `#2F2E2B80`)
136
+ - Sticky header separates with a subtle bottom border, no shadow
137
+ - Drawers and bottom sheets slide in with `0.325s ease-out` without elevation shadow
138
+
139
+ ## 7. Do's and Don'ts
140
+
141
+ ### Do
142
+ - Use `#7620F6` (or `#7351EC` in v4 DS contexts) exclusively as the brand accent; do not introduce other accent hues
143
+ - Keep primary CTAs black (#000) on white surfaces inside the product UI to respect luxury product photography
144
+ - Reserve the purple CTA only for high-emphasis marketing surfaces (landing pages, onboarding)
145
+ - Use Pretendard for all Korean and Latin text; ensure feature settings `"ss03"` `"cv01"` are active on mobile
146
+ - Apply 8px radius to product cards; 4px to chips, badges, and secondary UI elements
147
+ - Use red (#EC5151) exclusively for sale prices, error states, and count-down labels
148
+ - Respect the 20px mobile / 48px desktop horizontal padding consistently
149
+
150
+ ### Don't
151
+ - Don't use the purple as a button background inside the main shopping UI — the primary action is always black
152
+ - Don't add gradients or decorative patterns behind product images
153
+ - Don't reduce line-height below 1.5× for body text; the Pretendard variable-weight range demands adequate leading
154
+ - Don't mix the legacy 13px/1.2 line-height scale with the v4 rem scale in new components
155
+ - Don't use border-radius above 8px for cards or 4px for chips in the current DS
156
+
157
+ ## 8. Responsive Behavior
158
+
159
+ - **Mobile (≤480px):** 20px horizontal padding; 2-column product grid; bottom navigation bar replaces desktop header links; font sizes follow v4 rem scale with `font-feature-settings: "ss03","cv01"`
160
+ - **Tablet mini (480–768px):** 41px horizontal padding; 3-column grid; header retains logo + search; secondary nav collapsed
161
+ - **Tablet big (768–1024px):** full desktop header; 3–4 column grid; xPadding 41px
162
+ - **Desktop (1024–1200px):** 48px horizontal padding; max-content 1200px; full navigation visible
163
+ - **Large desktop (≥1200px):** content remains capped at 1200px; outer gutters expand
164
+
165
+ ## 9. Agent Prompt Guide
166
+
167
+ When generating Trenbe UI:
168
+ - Use white (`#FFFFFF`) canvas with warm near-black body text (`#2F2E2B`)
169
+ - Primary interactive element: black button (bg `#000`, text `#fff`, radius 2px)
170
+ - Use Pretendard font; specify weights 400 / 500 / 700
171
+ - Accent color: `#7620F6` for labels, links, and focus indicators only
172
+ - Red (`#EC5151`) for price reductions and error messages
173
+ - Green (`#1EB789`) for success confirmations
174
+ - Card containers: white bg, 1px `#CFCECB` border, 8px radius
175
+ - Do not add gradients, shadows, or decorative flourishes behind product images
176
+ - Layout: 20px mobile gutter, 48px desktop gutter, max-width 1200px
177
+
178
+ ## 10. Voice & Tone
179
+
180
+ **Three defining adjectives:** Trustworthy, Direct, Savvy
181
+
182
+ | Do | Don't |
183
+ |----|-------|
184
+ | Use clear, benefit-forward language: "verified by our experts" | Use vague luxury clichés: "elevate your lifestyle" |
185
+ | Be specific about tech advantages: "AI price matching across 10M items" | Use jargon: "synergize your fashion portfolio" |
186
+ | Acknowledge the user's desire directly: "가지고 싶은 것, 가지고 싶을 때" (what you want, when you want it) | Be passive or hedged: "you might consider exploring..." |
187
+ | Frame authentication as protection, not gatekeeping | Shame or intimidate around budget |
188
+
189
+ **Voice samples (illustrative):**
190
+ - "전세계 명품을 한 눈에. 최저가 스캐너로 가장 빠르게." — All the world's luxury, at a glance. Fastest with our lowest-price scanner. *(illustrative tone sample)*
191
+ - "가지고 싶은 것, 가지고 싶을 때, 가질 수 있도록." — What you want, when you want it, yours to have. *(illustrative brand line)*
192
+ - "300% 보상 정책으로 안심하고 쇼핑하세요." — Shop with peace of mind backed by our 300% compensation policy. *(illustrative trust copy)*
193
+
194
+ ## 11. Brand Narrative
195
+
196
+ Trenbe was founded in November 2016 in Seoul, South Korea, by CEO Park Kyung-hoon with a clear thesis: luxury shopping online did not have to mean counterfeit risk, opaque pricing, or limited selection. The name "Trenbe" blends "trend" and "be," signaling the brand's aspiration for customers to inhabit the trends they love rather than merely observe them. From the start, the company invested in physical infrastructure — overseas offices and fulfillment centers in the UK, US, France, Germany, Italy, and Japan — so that its platform could offer direct buying, direct inspection, and direct shipping rather than relying on third-party resellers.
197
+
198
+ The company describes itself as a "luxury tech-commerce" business rather than a simple marketplace. Its proprietary Trenbot AI scans roughly 10 million products in real time across global department stores, brand official sites, and outlets to surface the lowest available price. The Korean Authentic Center, initially an in-house inspection team of over 40 expert authenticators, was later spun off as an independent subsidiary to reinforce the credibility of Trenbe's authentication signal. In 2022 Trenbe raised a KRW 35 billion Series D round, and the platform's MAU reached 4.5 million by late 2020. By Q1 2026 the pre-owned luxury segment contributed to its first operating profit of KRW 300 million.
199
+
200
+ Trenbe's corporate manifesto — "We change the rules. We change the commerce." — anchors its design and product philosophy. The purple accent in its design system represents this disruption: vivid and technologically forward in a category traditionally expressed through ivory and gold. Yet the dominant use of black-and-white in the shopping UI maintains the respectful restraint that luxury buyers expect when their attention is focused on the products themselves.
201
+
202
+ ## 12. Principles
203
+
204
+ 1. **Trust through transparency** — Every price displayed shows where it came from. Every product that passes through Trenbe's fulfillment is inspected and logged. *UI implication:* Inspection status, price history, and authentication certificates appear prominently on product detail pages without requiring the user to dig for them.
205
+
206
+ 2. **Desire, not delay** — The user's intent is to acquire something desirable; friction between intent and checkout must be minimized. *UI implication:* Add-to-cart and purchase flows are surfaced within one thumb's reach on mobile; payment providers including Naver Pay, Apple Pay, and card-based installments are pre-integrated.
207
+
208
+ 3. **Global reach, local precision** — Trenbe operates in seven countries but the experience is built for Korean consumers first, then localized. *UI implication:* Korean-language copy is the primary string; KRW pricing is canonical; Pretendard is loaded as a subset for performance at Korean character density.
209
+
210
+ 4. **Technology as service, not spectacle** — AI and automation power the platform but should never make the user feel surveilled or overwhelmed. *UI implication:* Trenbot recommendations are labeled clearly as price comparisons, not "personalization magic"; data-driven badges use factual language ("15% below market average").
211
+
212
+ 5. **Authenticity as table stakes** — Counterfeit risk is the sector's defining anxiety. Trenbe's response is operational, not just copywritten. *UI implication:* Authentic guarantee badges and Korean Authentic Center certification marks appear at the same visual weight as the price; they are never buried in footers.
213
+
214
+ ## 13. Personas
215
+
216
+ *Illustrative Persona A — The Aspirational Accumulator:* A 28-year-old woman in Seoul in her first management-level role. She buys one statement luxury piece per quarter as a self-reward. She comparison-shops obsessively before committing. She trusts Trenbe's price scanner because she has verified its results against department-store prices herself. Her primary device is iPhone; she browses during commute and completes purchases at home.
217
+
218
+ *Illustrative Persona B — The Resale Strategist:* A 35-year-old man who buys luxury goods both to enjoy and to trade. He uses the Shuffle exchange feature to upgrade without net spend. He values the authentication certificate because it protects the resale value of his items. He reads the tech blog and trusts the brand's engineering transparency.
219
+
220
+ *Illustrative Persona C — The Gifting Professional:* A 40-year-old professional who buys 3–5 luxury gifts per year for clients and family. She needs fast, reliable delivery and gift-wrap options. She cares deeply about authenticity guarantees because a counterfeit gift would be a professional embarrassment. The 300% compensation policy is the feature that converted her.
221
+
222
+ *Illustrative Persona D — The Global Shopper:* A Korean expat in London who relies on Trenbe's UK office logistics to ship authenticated pieces home to family at prices competitive with local boutiques. She reads prices in KRW and filters by brand country of origin.
223
+
224
+ ## 14. States
225
+
226
+ - **Empty:** Category page with no matching items shows a centered purple icon + "검색 결과가 없어요" (No results found) in gray900 text with a suggestion to broaden the filter — no full-bleed illustration, no decorative artwork
227
+ - **Loading (skeleton):** Product cards show a gray100 (#F7F6F5) block for the image area and two gray200 (#EFEEEB) lines for title/price; animated with a left-to-right shimmer using linear gradient
228
+ - **Error — Network:** Full-page or modal overlay on a white background with a neutral icon, headline in gray1000, sub-copy in gray800, and a black "다시 시도" (Retry) CTA button
229
+ - **Error — Validation:** Inline beneath form fields; border turns red (#EC5151), helper text appears in red500 at 1.2rem (12px), field label does not change color
230
+ - **Success:** Green (#1EB789) inline confirmation text or toast; checkmark icon at 24px; auto-dismisses after 3s with 0.2s ease-in-out fade-out
231
+ - **Skeleton (product detail):** Hero image area dims to gray200, product title and price lines show as gray100 bars; below-fold specs show 3-line skeleton
232
+ - **Disabled:** Button opacity 0.65; text color switches to #AFAEAB (gray600/textDisabled); cursor changes to not-allowed; no background color change
233
+
234
+ ## 15. Motion & Easing
235
+
236
+ **Duration scale:**
237
+ - Micro: 150ms — border-color focus transitions (`ease-in-out`)
238
+ - Short: 200ms — hover state color changes, icon rotations (`ease-in-out`)
239
+ - Medium: 240ms — drawer bottom position animations (`ease`)
240
+ - Standard: 325ms — modal entrance/exit, overlay opacity (`ease-out`)
241
+ - Long: 400ms — page-level slide carousels (`linear`)
242
+
243
+ **Primary easing functions:**
244
+ - `ease-in-out` — default for interactive state changes (border, background-color)
245
+ - `ease-out` — modal fade-in and slide-up (enters fast, settles gently)
246
+ - `linear` — carousel swipe momentum, progress bars
247
+
248
+ **Rules:**
249
+ - All transitions are applied via CSS `transition` properties; no JavaScript-driven animation for routine UI state changes
250
+ - Skeleton shimmer uses a CSS linear-gradient animation at 1.5s infinite linear (AOS library handles scroll-triggered reveals)
251
+ - Carousel interactions use Swiper.js inertia; drag velocity maps naturally without artificial easing override
252
+ - Reduced-motion: no explicit `prefers-reduced-motion` override is in the current CSS; agents generating new components must add it
@@ -0,0 +1,227 @@
1
+ ---
2
+ id: voicetube
3
+ name: "VoiceTube"
4
+ country: TW
5
+ category: education
6
+ homepage: "https://tw.voicetube.com"
7
+ primary_color: "#7E3AAF"
8
+ logo:
9
+ type: favicon
10
+ slug: "https://www.google.com/s2/favicons?domain=tw.voicetube.com&sz=256"
11
+ verified: "2026-06-03"
12
+ omd: "0.1"
13
+ ---
14
+
15
+ # VoiceTube
16
+
17
+ Asia's largest video-based English learning platform, blending immersive YouTube content with AI-powered study tools under a dramatic dark-purple brand identity.
18
+
19
+ ## 1. Visual Theme & Atmosphere
20
+
21
+ VoiceTube presents a consistently dark, cinematic atmosphere built around a deep purple-to-near-black canvas. The body background is `#18131D` — a dark aubergine that evokes a premium streaming experience rather than a conventional classroom — while the navigation bar graduates from `#210040` to `#1D102B` as a horizontal gradient, anchoring every page in the brand's deepest signature hues. Primary call-to-action elements arrive as pill-shaped buttons bearing a vivid diagonal gradient from `#653AAF` to `#A73AAF`, injecting a pop of electric violet that reads immediately as the interactive signal layer. Secondary surfaces such as the sidebar and product grid use `#251633`, creating a subtle two-stop dark elevation system. The overall mood is energetic and night-mode-native: think streaming platform meets study app, where video thumbnail cards float on near-black panels, typography in soft white-gray (`#E3E3E3`) maintains clarity without harshness, and playful gradient accents ensure the interface never feels heavy or academic.
22
+
23
+ ## 2. Color Palette & Roles
24
+
25
+ - **Brand Purple 400 (Primary):** `#7E3AAF` — interactive accents, badge fills, focused input borders
26
+ - **Brand Purple 300 (Vibrant):** `#A73AAF` — gradient endpoint, hover highlights, accent text on dark
27
+ - **Button Gradient Start:** `#653AAF` — primary CTA gradient origin (135.73°)
28
+ - **Deep Purple 500:** `#210040` — navigation gradient start, deepest surface overlays
29
+ - **Dark Aubergine 800 (Background):** `#18131D` — body canvas, default page background
30
+ - **Surface 600:** `#251633` — sidebar background, secondary card surfaces
31
+ - **Nav Gradient End:** `#1D102B` — navigation bar right edge
32
+ - **Purple Tint 100:** `#E0D0EC` — light tag fill, subtle selected state
33
+ - **White:** `#FFFFFF` — primary text on gradient buttons
34
+ - **Gray 300 (Body Text):** `#E3E3E3` — body text on dark, outline button text and border
35
+ - **Gray 400 (Secondary):** `#B4B4B4` — secondary labels, disabled state text
36
+ - **Gray 500 (Muted):** `#9CA3AF` — placeholder text
37
+ - **Error:** `#ED4F55` — danger borders, validation messages
38
+ - **Success:** `#33991D` — correct answers, achievement indicators
39
+ - **Link/Info:** `#4283E4` — hyperlink color, informational icons
40
+
41
+ ## 3. Typography Rules
42
+
43
+ VoiceTube's global body font is a CJK-first stack: `-apple-system, BlinkMacSystemFont, PingFang TC, PingFang SC, Hiragino Sans, Hiragino Kaku Gothic ProN, Microsoft JhengHei, Microsoft YaHei, Meiryo, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif`. This stack ensures Mandarin and Japanese characters render optimally before falling back to Latin fonts. The base font weight is `400`, body text is set in `#E3E3E3` for comfortable contrast on dark backgrounds, and line-height defaults to `1.425` (the `vc-leading-base` token). The proprietary logomark typeface features 60° slanted stroke extensions that echo the play-button triangle — this angular DNA is brand-exclusive and not replicated in UI text. The type scale in use: `xs = 12px`, `sm = 14px`, `base/md = 16px`, `lg = 18px`, `xl = 20px`, `2xl = 24px`. Weight tokens span from `vc-font-light (300)` through `vc-font-normal (400)`, `vc-font-medium (500)`, `vc-font-semibold (600)`, `vc-font-bold (700)`, to `vc-font-black (900)`. Navigation labels and primary UI text use `500 (medium)`.
44
+
45
+ ## 4. Component Stylings
46
+
47
+ ### Primary CTA Button
48
+
49
+ **Gradient Filled**
50
+ - Background: `linear-gradient(135.73deg, #653AAF, #A73AAF 98.77%)`
51
+ - Text: `#FFFFFF`
52
+ - Radius: 9999px
53
+ - Height: 40px
54
+ - Padding: 0 20px
55
+ - Font: 16px / 500
56
+
57
+ ### Outline Button
58
+
59
+ **Ghost / Secondary**
60
+ - Background: `transparent`
61
+ - Text: `#E3E3E3`
62
+ - Border: 1px solid `#E3E3E3`
63
+ - Radius: 9999px
64
+ - Height: 40px
65
+ - Padding: 0 20px
66
+ - Font: 16px / 500
67
+
68
+ ### Navigation Bar
69
+
70
+ **Top Nav**
71
+ - Background: `linear-gradient(to right, #210040, #1D102B)`
72
+ - Text: `#E3E3E3`
73
+ - Height: 64px
74
+
75
+ ### Sidebar Nav Item
76
+
77
+ **Active / Hover**
78
+ - Background: `rgba(126, 58, 175, 0.2)`
79
+ - Text: `#E3E3E3`
80
+ - Padding: 12px 8px
81
+ - Font: 14px / 400
82
+ - Radius: 0px
83
+
84
+ ### Tag / Badge Chip
85
+
86
+ **Brand Tag**
87
+ - Background: `#7E3AAF`
88
+ - Text: `#E3E3E3`
89
+ - Radius: 4px
90
+ - Padding: 2.5px 12px
91
+ - Font: 14px / 400
92
+
93
+ ### Text Input
94
+
95
+ **Default State**
96
+ - Background: `transparent`
97
+ - Text: `#E3E3E3`
98
+ - Border: 1px solid `#E3E3E3`
99
+ - Radius: 4px
100
+
101
+ **Focus / Hover**
102
+ - Border: 1px solid `#7E3AAF`
103
+
104
+ **Disabled**
105
+ - Text: `#B4B4B4`
106
+
107
+ **Error**
108
+ - Border: 1px solid `#ED4F55`
109
+ - Text: `#ED4F55`
110
+
111
+ ---
112
+ **Verified:** 2026-06-03
113
+ **Tier 1 sources:** https://tw.voicetube.com (homepage HTML + CSS `/_next/static/css/8c750a42d09ad582.css`); https://tw.voicetube.com/branding (brand page — structure + JS chunk `page-7cb732e6cbfefad2.js`); https://tw.voicetube.com/about (brand narrative)
114
+ **Tier 2 sources:** getdesign.md/voicetube — NOT LISTED ("No designs found for 'voicetube'"). refero — no result found for VoiceTube.
115
+ **Conflicts unresolved:** none
116
+
117
+ ## 5. Layout Principles
118
+
119
+ VoiceTube uses a responsive two-column shell on desktop: a fixed 110px-wide sidebar navigation column paired with a fluid content area (`vc-grid-cols-[110px_1fr]`), which collapses to single-column on mobile (`lg:vc-grid-cols-1`). Content grids for video cards typically use 3–4 columns on large screens, scaling down through 2-column tablet to single-column mobile layouts. Horizontal container padding follows the Tailwind spacing scale (`p-4` = 16px, `p-5` = 20px, `p-6` = 24px). The overall max content width is not capped artificially narrow — VoiceTube leans into wide viewports for its video-first grid. Sticky positioning is used for the top navigation bar at `z-[800]`, ensuring it floats above all content as users scroll through dense video libraries.
120
+
121
+ ## 6. Depth & Elevation
122
+
123
+ VoiceTube's elevation model is purely dark-stack-based — layers are distinguished by background darkness rather than box-shadows. The four tiers in practice: **L0** `#18131D` (body canvas), **L1** `#1D102B–#210040` (navigation, modal backdrops), **L2** `#251633` (sidebar panels, card surfaces), **L3** `#2B2B2B` (dark-500 floating elements, tooltips). Genuine `box-shadow` usage is sparse; focus states use a thin `0 0.1px 0 0 #7E3AAF` outline-shadow rather than a spread shadow, keeping the UI graphically flat. Modal/overlay z-indices follow a defined stack: content at 100, sticky nav at 800, modals at 1200–1299, toasts/notifications at 1500–1600.
124
+
125
+ ## 7. Do's and Don'ts
126
+
127
+ ### Do
128
+ - Use the `linear-gradient(135.73deg, #653AAF, #A73AAF)` gradient as the primary CTA fill to maintain brand energy
129
+ - Apply `#7E3AAF` for interactive focus rings, active link states, and selected filters
130
+ - Use pill-shaped buttons (`border-radius: 9999px`) for all standalone CTA actions
131
+ - Keep body backgrounds in the `#18131D` to `#251633` range for dark-mode consistency
132
+ - Use `#E3E3E3` (gray-300) as the default text color on dark surfaces
133
+ - Prefer the CJK-inclusive font stack for any Mandarin or Japanese content
134
+ - Apply `ease-out` timing for all UI transitions at 150ms or 300ms
135
+
136
+ ### Don't
137
+ - Don't use high-saturation colors from outside the purple / teal / orange product palette without clear context
138
+ - Don't apply gradient buttons to destructive actions (delete, remove) — use plain `#ED4F55` borders instead
139
+ - Don't use light backgrounds (`#FFFFFF`, `#F3F3F3`) as page canvas — VoiceTube is dark-mode-native
140
+ - Don't mix border-radius: square corners would clash with the brand's pill-and-round language
141
+ - Don't stack multiple box-shadows — elevation is communicated through dark tone layering, not shadow spreads
142
+ - Don't reduce font weight below 400 for UI text at `14px` or smaller on dark surfaces
143
+
144
+ ## 8. Responsive Behavior
145
+
146
+ VoiceTube's breakpoint system uses Tailwind's `md` (768px) and `lg` (1024px) prefixes extensively. The pattern `md:vc-h-9 md:vc-text-sm` on buttons demonstrates size reduction at tablet widths: button height drops from 40px to 36px and font from 16px to 14px. The sidebar collapses at `lg:vc-hidden`. Video grids shift from a multi-column layout to a vertically stacked, horizontally scrollable carousel on mobile. Hero section typography is set in viewport-relative units (`4.167vw` headline on desktop, `6.4vw` on mobile), ensuring the brand message fills the screen at every size. Padding contracts gracefully: `p-6` on desktop drops to `p-4` on mobile throughout content areas.
147
+
148
+ ## 9. Agent Prompt Guide
149
+
150
+ When building VoiceTube-style UI:
151
+ - Dark canvas: body background `#18131D`, sidebar `#251633`, nav `linear-gradient(to right, #210040, #1D102B)`
152
+ - Primary button: `border-radius: 9999px`, `height: 40px`, `padding: 0 20px`, `background: linear-gradient(135.73deg, #653AAF, #A73AAF)`, `color: #FFFFFF`, `font-weight: 500`, `font-size: 16px`
153
+ - Outline button: same shape, `background: transparent`, `border: 1px solid #E3E3E3`, `color: #E3E3E3`
154
+ - Focus ring: `box-shadow: 0 0 0 1px #7E3AAF`
155
+ - Error state: `border-color: #ED4F55`, `color: #ED4F55`
156
+ - Disabled: `color: #B4B4B4`, `cursor: not-allowed`
157
+ - Transitions: `transition-duration: 150ms`, `transition-timing-function: ease-out`
158
+ - Font stack: `-apple-system, BlinkMacSystemFont, "PingFang TC", "PingFang SC", "Microsoft JhengHei", sans-serif`
159
+
160
+ ## 10. Voice & Tone
161
+
162
+ VoiceTube's copy voice is **energetic, inclusive, encouraging** — a friendly study partner who celebrates small wins without condescension.
163
+
164
+ | Dimension | Do | Don't |
165
+ |---|---|---|
166
+ | Register | Warm, conversational, direct | Academic, formal, passive |
167
+ | Pacing | Short punchy sentences | Long subordinate clause chains |
168
+ | CTA framing | "開始學習" / "免費加入" | "Please register to access content" |
169
+ | Achievement framing | "你做到了!" / "再接再厲" | "Your score was insufficient" |
170
+ | Error framing | "再試一次" (try again) | "An error has occurred" |
171
+
172
+ Voice samples (illustrative):
173
+ - *Illustrative — homepage headline register:* "連結世界、享受學習樂趣!" — Connect to the world, enjoy learning. No jargon, no friction, pure invitation.
174
+ - *Illustrative — product description:* "每天20分鐘跟著練習,即可獲得30天免費訂閱制" — Specific promise, specific time, specific reward. Numbers over adjectives.
175
+ - *Illustrative — branding tagline:* "Connect. Have fun!" — Two words each. The brand thinks in micro-copy that works across any language boundary.
176
+
177
+ ## 11. Brand Narrative
178
+
179
+ VoiceTube launched in 2013 when three co-founders — Zenn, Lai, and Tsai — were teaching themselves English after work using YouTube videos. What started as a personal workaround became a community: a place where millions of Mandarin speakers could learn the world's lingua franca through the authentic, conversational content they actually wanted to watch. The insight was simple but powerful — context-rich video beats rote drilling, and the best classroom is one you choose to enter.
180
+
181
+ From that origin story, VoiceTube built "Asia's largest online education platform," expanding from a curated video library into a full learning ecosystem: the core VoiceTube app for daily video-driven practice, Hero for AI-adaptive test preparation, Vclass for live celebrity-instructor courses, and Dictionary for deep vocabulary acquisition with dual-accent audio. The brand's name fuses "voice" (spoken language output, the hardest skill to acquire) with "tube" (YouTube's video-native learning environment), and the logo literalises this: a play triangle, the letter V, and a checkmark merged into a single mark whose 60°-angle typography echoes the geometry of the play button across every letterform.
182
+
183
+ Today, with over 5 million users and recognition including Facebook App of the Year 2016, VoiceTube positions itself not as a language-learning tool but as a social platform for human connection through English — summarised in its rallying cry: **"Connect. Have fun!"**
184
+
185
+ ## 12. Principles
186
+
187
+ 1. **Video-first learning.** Real language lives in authentic video, not curated sentences. Every feature exists to make video consumption study-worthy. *UI implication:* Video thumbnails are the primary content atom; card grids prioritize visual poster art with minimal text overlay.
188
+
189
+ 2. **Connection over completion.** Learning English is a means to connect with people and ideas worldwide, not a checkmark. *UI implication:* Social indicators (learner counts, challenge rankings) are surfaced prominently to reinforce the community dimension.
190
+
191
+ 3. **Guided, not gatekept.** The platform meets learners where they are and provides direction without testing walls. *UI implication:* Free content is broadly accessible; premium features are surfaced contextually via gentle gradient CTAs rather than locked-content banners.
192
+
193
+ 4. **Innovation in every layer.** From AI vocabulary recommendations to gamified pronunciation challenges, technology should make learning feel effortless. *UI implication:* AI-powered elements are woven into the core flow — word lookup inline, difficulty auto-adjustment, personalised ranking — rather than siloed as premium add-ons.
194
+
195
+ 5. **Diverse and compound identity.** The brand serves Mandarin, Japanese, and Vietnamese speakers, and the design must flex accordingly. *UI implication:* CJK-optimized font stacks, locale-aware copy, and multi-lingual navigation are non-negotiable baseline requirements.
196
+
197
+ ## 13. Personas
198
+
199
+ *Illustrative archetype — The Ambitious Professional:* A 28-year-old Taipei product manager who needs business English to advance her career. She has 20–30 minutes on her commute. She uses VoiceTube for daily video practice and Hero for TOEIC prep. She values progress indicators and streak mechanics that prove the time investment is working.
200
+
201
+ *Illustrative archetype — The Pop-Culture Learner:* A 19-year-old university student in Taichung who watches English-language YouTube unsubbed but misses nuance. He found VoiceTube through a gaming channel and now uses the dictionary for slang. He values content discovery and social sharing over structured curricula.
202
+
203
+ *Illustrative archetype — The Late-Stage Returner:* A 45-year-old parent in Kaohsiung who studied English in school, let it lapse, and now wants to rebuild for their child's international schooling applications. She learns through Vclass with established instructors. She values warmth, patience, and celebrity-instructor credibility over gamification.
204
+
205
+ *Illustrative archetype — The Japanese Exchange Student:* A 22-year-old from Osaka studying at NTU who uses VoiceTube's Japanese-locale version to practice English while picking up Mandarin via Taiwanese content. He values the multi-locale product suite and dual-accent dictionary features.
206
+
207
+ ## 14. States
208
+
209
+ - **Empty — no search results:** Dark panel with centered illustration and copy "找不到相關影片,試試其他關鍵字" — purple-tinted icon at low opacity against `#18131D`, no hard borders.
210
+ - **Loading — video grid:** Skeleton cards in `#251633` with subtle shimmer animation sweeping left-to-right in `#1D102B`, matching the existing dark elevation tier so the layout shift is imperceptible.
211
+ - **Error — form validation:** Input border shifts to `1px solid #ED4F55`, placeholder text and error label render in `#ED4F55`, the field label color also shifts to match.
212
+ - **Error — page/server:** Full-page dark overlay at `#18131D` with a centered 404/500 message in `#E3E3E3`, no gradient backgrounds — keeps the dark canvas consistent.
213
+ - **Success — quiz answer correct:** `#33991D` confirmation background at 10% opacity (`rgba(51, 153, 29, 0.1)`) with a full-opacity `#33991D` border and checkmark icon.
214
+ - **Skeleton — video card:** `#251633` block sized to thumbnail aspect-ratio with a `#1D102B` shimmer pulse at 0.8s ease-in-out; title line represented by two `#251633` rounded bars.
215
+ - **Disabled — button:** `opacity: 0.6`, `cursor: not-allowed`, text shifts to `#B4B4B4`, gradient or border color desaturates; no hover effect fires.
216
+
217
+ ## 15. Motion & Easing
218
+
219
+ VoiceTube uses a compact three-step duration scale anchored in Tailwind custom tokens:
220
+
221
+ | Token | Value | Use |
222
+ |---|---|---|
223
+ | `vc-duration-sm` | 86ms | Micro-interactions: hover color shifts, focus ring appearance |
224
+ | `vc-duration-md` | 150ms | Standard transitions: button state changes, border-color, background-color |
225
+ | `vc-duration-xl` | 300ms | Page-level: slide-in panels, carousel advances, modal entrance |
226
+
227
+ The default easing is `ease-out` (`vc-ease-base`) — elements decelerate into their resting position, conveying arrival and completion. `cubic-bezier(0.4, 0, 0.2, 1)` (`vc-ease-in-out`) appears on keyboard-focus indicators and bi-directional carousels where enter and exit need to be symmetrical. Carousel progress dots use a width-transition at `vc-duration-md / ease-out` (active dot expands from 10px to 30px). No spring/bounce physics — the brand is energetic through color and gradient, not through over-engineered motion.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/install-skills.ts","../src/core/agent-detect.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport {\n readFileSync,\n readdirSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n} from 'node:fs';\nimport { join, dirname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { detectInstalledAgents } from '../core/agent-detect.js';\n\nexport type SkillTarget = 'claude-code' | 'codex' | 'opencode';\n\nexport interface InstallSkillsOptions {\n dir?: string;\n agents?: SkillTarget[];\n force?: boolean;\n /** Non-interactive: install all skills + all agents without TUI prompt.\n * Default false → interactive multiselect when TTY available. */\n all?: boolean;\n /** Pre-select specific skill names from CLI flag (`--skills omd-init,omd-apply`).\n * Overrides interactive prompt when set. */\n skillsFilter?: string[];\n /** Pre-select specific agent names. Overrides interactive prompt when set. */\n agentsFilter?: string[];\n}\n\ninterface InstallPlan {\n target: SkillTarget;\n destDir: string;\n layout: 'folder' | 'flat';\n}\n\nfunction findPackageRoot(): string | null {\n let cur = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 8; i++) {\n if (existsSync(join(cur, 'skills'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nfunction listShippedSkills(packageRoot: string): string[] {\n const skillsDir = join(packageRoot, 'skills');\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir)\n .filter((name) => existsSync(join(skillsDir, name, 'SKILL.md')))\n .sort();\n}\n\n/**\n * Canonical agent definitions live at `agents/<name>.md` (markdown with YAML\n * frontmatter). Channel-specific files (.claude/agents/*.md, .codex/agents/*.toml)\n * are generated artifacts — never the source of truth.\n *\n * The package ships only `agents/` and the generator emits per-channel files\n * into the user's project on `omd install-skills`.\n */\nfunction listCanonicalAgents(packageRoot: string): string[] {\n const dir = join(packageRoot, 'agents');\n if (!existsSync(dir)) return [];\n return readdirSync(dir)\n .filter((name) => name.startsWith('omd-') && name.endsWith('.md'))\n .sort();\n}\n\ninterface ParsedAgent {\n name: string;\n description: string;\n tools: string[];\n model: string;\n body: string;\n}\n\n/** Parse `agents/<name>.md` YAML frontmatter + body into structured form. */\nfunction parseCanonicalAgent(packageRoot: string, filename: string): ParsedAgent {\n const src = readFileSync(join(packageRoot, 'agents', filename), 'utf8');\n const match = /^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/.exec(src);\n if (!match) {\n throw new Error(`agents/${filename}: missing YAML frontmatter`);\n }\n const fm = match[1];\n const body = match[2];\n const grab = (key: string): string => {\n const re = new RegExp(`^${key}:\\\\s*(.+)$`, 'm');\n const m = re.exec(fm);\n return m ? m[1].trim().replace(/^[\"']|[\"']$/g, '') : '';\n };\n return {\n name: grab('name') || filename.replace(/\\.md$/, ''),\n description: grab('description'),\n tools: grab('tools')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean),\n model: grab('model') || 'sonnet',\n body,\n };\n}\n\n/** Map Claude tool names to Codex tool names (best-effort). */\nfunction claudeToolsToCodex(tools: string[]): string[] {\n const m: Record<string, string> = {\n Read: 'read_file',\n Write: 'write_file',\n Edit: 'edit_file',\n Bash: 'shell',\n Glob: 'search',\n Grep: 'search',\n WebFetch: 'web_fetch',\n WebSearch: 'search',\n Agent: 'spawn_agent',\n TaskCreate: 'task',\n TaskUpdate: 'task',\n TaskList: 'task',\n };\n const out = new Set<string>();\n for (const t of tools) out.add(m[t] ?? t.toLowerCase());\n return [...out].sort();\n}\n\n/** Map Claude model alias to Codex/OpenAI model id (best-effort). */\nfunction claudeModelToCodex(model: string): string {\n const m: Record<string, string> = {\n haiku: 'gpt-4.1-mini',\n sonnet: 'gpt-4.1',\n opus: 'gpt-4.1',\n };\n return m[model.toLowerCase()] ?? 'gpt-4.1';\n}\n\n/** Render a canonical agent as a Claude Code subagent file.\n * IMPORTANT: Claude Code's subagent parser requires YAML frontmatter (`---`)\n * as the FIRST line of the file. Any preceding content (HTML comments, blank\n * lines) breaks discovery. So we encode the managed-by-omd marker as a\n * custom frontmatter field (`omd_managed: true`) instead of an HTML comment.\n */\nfunction renderClaudeAgent(a: ParsedAgent): string {\n const fm = [\n '---',\n `name: ${a.name}`,\n `description: ${a.description}`,\n `tools: ${a.tools.join(', ')}`,\n `model: ${a.model}`,\n `omd_managed: true`,\n '---',\n '',\n ].join('\\n');\n return fm + a.body;\n}\n\n/** Render a canonical agent as a Codex TOML file (declarative pointer). */\nfunction renderCodexAgent(a: ParsedAgent): string {\n const tools = claudeToolsToCodex(a.tools);\n const model = claudeModelToCodex(a.model);\n const desc = a.description.replace(/\"/g, '\\\\\"');\n return [\n `[agent]`,\n `name = \"${a.name}\"`,\n `description = \"${desc}\"`,\n `model = \"${model}\"`,\n `max_threads = 1`,\n `allowed_tools = [${tools.map((t) => `\"${t}\"`).join(', ')}]`,\n '',\n `instructions = \"\"\"`,\n `Source of truth: agents/${a.name}.md (canonical). The full role spec is`,\n `mirrored to .claude/agents/${a.name}.md when installed for Claude Code.`,\n `Follow that spec verbatim regardless of channel.`,\n '',\n `Codex notes:`,\n `- Spawn sub-agents via spawn_agent with names matching .codex/agents/<name>.toml`,\n `- Use shell to invoke CLI helpers (omd init prepare, omd remember, git apply, npx axe-core, npx lighthouse)`,\n `- All artifacts go inside .omd/runs/run-<latest>/ (or skills/omd-lab-02-design-harness/runs/<lab-version>-...)`,\n `\"\"\"`,\n '',\n ].join('\\n');\n}\n\nfunction planForTarget(projectRoot: string, target: SkillTarget): InstallPlan {\n switch (target) {\n case 'claude-code':\n return {\n target,\n destDir: join(projectRoot, '.claude', 'skills'),\n layout: 'folder',\n };\n case 'codex':\n return {\n target,\n destDir: join(projectRoot, '.codex', 'skills'),\n layout: 'folder',\n };\n case 'opencode':\n return {\n target,\n destDir: join(projectRoot, '.opencode', 'agents'),\n layout: 'flat',\n };\n }\n}\n\nconst MANAGED_HEADER =\n '<!-- omd:installed-skill — managed by `omd install-skills`. Do not edit; rerun the command to refresh. -->';\n\ninterface InstallResult {\n target: SkillTarget;\n skill: string;\n destPath: string;\n status: 'created' | 'updated' | 'unchanged' | 'skipped-drift';\n}\n\nfunction installOne(\n packageRoot: string,\n plan: InstallPlan,\n skill: string,\n force: boolean\n): InstallResult {\n const src = readFileSync(\n join(packageRoot, 'skills', skill, 'SKILL.md'),\n 'utf8'\n );\n const managed = MANAGED_HEADER + '\\n\\n' + src;\n const destPath =\n plan.layout === 'folder'\n ? join(plan.destDir, skill, 'SKILL.md')\n : join(plan.destDir, skill + '.md');\n\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n if (exists && existing === managed) {\n return { target: plan.target, skill, destPath, status: 'unchanged' };\n }\n\n if (exists && !existing.startsWith(MANAGED_HEADER) && !force) {\n return { target: plan.target, skill, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, managed, 'utf8');\n return {\n target: plan.target,\n skill,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\n/** Install a hook script from package's .claude/hooks/ to project. */\nfunction installHookFile(\n packageRoot: string,\n projectRoot: string,\n filename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = 'claude-code';\n const skillLabel = `hook:${filename}`;\n const srcPath = join(packageRoot, '.claude', 'hooks', filename);\n const destPath = join(projectRoot, '.claude', 'hooks', filename);\n\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src);\n return { target, skill: skillLabel, destPath, status: exists ? 'updated' : 'created' };\n}\n\n/**\n * Install / merge .claude/settings.json. We MERGE hooks (don't clobber user\n * customizations) by checking if the omd-managed `_doc` field is present.\n * Without --force, if a user-edited settings.json exists (no _doc field),\n * we skip with `skipped-drift`.\n */\nfunction installSettingsJson(\n packageRoot: string,\n projectRoot: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = 'claude-code';\n const skillLabel = 'settings:.claude/settings.json';\n const srcPath = join(packageRoot, '.claude', 'settings.json');\n const destPath = join(projectRoot, '.claude', 'settings.json');\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n // Check if it's the omd-managed version\n try {\n const parsed = JSON.parse(existing);\n if (typeof parsed._doc === 'string' && parsed._doc.includes('OmD')) {\n // managed — overwrite\n } else {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n } catch {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n }\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src);\n return { target, skill: skillLabel, destPath, status: exists ? 'updated' : 'created' };\n}\n\n/**\n * Copy a read-only data asset (reference-fingerprints.json, vocabulary.json, …)\n * from the package's `data/` into the project's `.claude/data/` or `.codex/data/`.\n * The skill reads these at runtime — they replace the deprecated `omd init recommend` CLI.\n */\nfunction installDataFile(\n packageRoot: string,\n projectRoot: string,\n channelDir: string,\n dataFilename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = channelDir === '.claude' ? 'claude-code' : 'codex';\n const skillLabel = `data:${dataFilename}`;\n\n const srcPath = join(packageRoot, 'data', dataFilename);\n const destPath = join(projectRoot, channelDir, 'data', dataFilename);\n\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n // Data files are pure copies — no managed header (would corrupt JSON).\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n // Honor user customization unless --force\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src, 'utf8');\n return {\n target,\n skill: skillLabel,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\n/**\n * Generate a per-channel agent file from the canonical `agents/<name>.md`.\n *\n * Channel = 'claude' → emits `.claude/agents/<name>.md` (markdown w/ frontmatter)\n * Channel = 'codex' → emits `.codex/agents/<name>.toml` (TOML pointer)\n */\nfunction installAgentFile(\n packageRoot: string,\n projectRoot: string,\n channel: 'claude' | 'codex',\n filename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = channel === 'claude' ? 'claude-code' : 'codex';\n const skillLabel = `agent:${filename}`;\n\n const parsed = parseCanonicalAgent(packageRoot, filename);\n const rendered =\n channel === 'claude' ? renderClaudeAgent(parsed) : renderCodexAgent(parsed);\n\n const destFilename =\n channel === 'claude' ? filename : filename.replace(/\\.md$/, '.toml');\n const destPath = join(\n projectRoot,\n channel === 'claude' ? '.claude' : '.codex',\n 'agents',\n destFilename\n );\n\n // For Claude Code: managed marker is encoded as `omd_managed: true` INSIDE the\n // frontmatter (rendered above) — no HTML comment can precede `---` or the\n // subagent loader rejects the file.\n // For Codex: TOML allows leading comments, so `# omd:installed-agent ...` is fine.\n const managed =\n channel === 'claude'\n ? rendered\n : '# omd:installed-agent — generated from agents/' +\n filename +\n ' by `omd install-skills`. Do not edit; rerun the command to refresh.\\n\\n' +\n rendered;\n\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n if (exists && existing === managed) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n\n // Drift detection sentinels:\n // Claude — look for `omd_managed: true` line inside frontmatter\n // Codex — look for `# omd:installed-agent` comment\n const isManaged =\n channel === 'claude'\n ? /\\nomd_managed:\\s*true\\b/.test(existing)\n : existing.startsWith('# omd:installed-agent');\n\n if (exists && !isManaged && !force) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, managed, 'utf8');\n return {\n target,\n skill: skillLabel,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\nconst STATUS_LABEL: Record<InstallResult['status'], string> = {\n created: pc.green('created'),\n updated: pc.cyan('updated'),\n unchanged: pc.dim('unchanged'),\n 'skipped-drift': pc.yellow('skipped'),\n};\n\nfunction autoDetectTargets(projectRoot: string): SkillTarget[] {\n const presence = detectInstalledAgents(projectRoot);\n const targets: SkillTarget[] = [];\n if (presence.claudeCode) targets.push('claude-code');\n if (presence.codex) targets.push('codex');\n if (presence.opencode) targets.push('opencode');\n // Cursor uses .mdc rules, not skills — installed via `omd sync`.\n if (targets.length === 0) {\n // Fallback: install for all three so user gets coverage even without\n // explicit signal. Idempotent so cost is low.\n return ['claude-code', 'codex', 'opencode'];\n }\n return targets;\n}\n\nexport async function runInstallSkills(\n opts: InstallSkillsOptions = {}\n): Promise<number> {\n const projectRoot = opts.dir ?? process.cwd();\n const packageRoot = findPackageRoot();\n if (!packageRoot) {\n console.error(pc.red('omd install-skills: package data not found'));\n return 1;\n }\n\n const allSkills = listShippedSkills(packageRoot);\n if (allSkills.length === 0) {\n console.error(pc.red('omd install-skills: no skills found in package'));\n return 1;\n }\n const allAgents = listCanonicalAgents(packageRoot);\n\n const force = opts.force ?? false;\n\n p.intro(\n pc.bold('omd install-skills') +\n pc.dim(` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n\n // Resolve selection: --all flag, --skills/--agents/--agent filter, or interactive TUI.\n // TUI runs only when stdin is a TTY and the corresponding flag isn't set.\n const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);\n const nonInteractive = opts.all || !isTTY || opts.skillsFilter || opts.agentsFilter;\n\n const detected = autoDetectTargets(projectRoot);\n // Real presence (not the all-3 fallback) — used purely for hint labels.\n const presence = detectInstalledAgents(projectRoot);\n const actuallyDetected: SkillTarget[] = [\n presence.claudeCode ? 'claude-code' : null,\n presence.codex ? 'codex' : null,\n presence.opencode ? 'opencode' : null,\n ].filter((x): x is SkillTarget => x !== null);\n\n let skills: string[];\n let canonicalAgents: string[];\n let targets: SkillTarget[];\n\n if (nonInteractive) {\n // Non-interactive resolution\n skills = opts.skillsFilter\n ? allSkills.filter((s) => opts.skillsFilter!.includes(s))\n : allSkills;\n canonicalAgents = opts.agentsFilter\n ? allAgents.filter((a) => opts.agentsFilter!.includes(a.replace(/\\.md$/, '')))\n : allAgents;\n targets = opts.agents\n ? opts.agents\n : opts.all\n ? (['claude-code', 'codex', 'opencode'] as SkillTarget[])\n : detected;\n } else {\n // === Interactive TUI — skill → subagent → channel order ===\n // 1. Skills (default = ALL selected)\n const skillResult = await p.multiselect({\n message:\n 'Skills · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allSkills.map((s) => ({ value: s, label: s, hint: 'omd skill' })),\n initialValues: allSkills,\n required: true,\n });\n if (p.isCancel(skillResult)) {\n p.cancel('Install cancelled.');\n return 130;\n }\n skills = skillResult as string[];\n\n // 2. Sub-agents (default = ALL selected)\n if (allAgents.length > 0) {\n const agentResult = await p.multiselect({\n message:\n 'Sub-agents · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allAgents.map((a) => ({\n value: a,\n label: a.replace(/\\.md$/, ''),\n hint: 'subagent',\n })),\n initialValues: allAgents,\n required: false,\n });\n if (p.isCancel(agentResult)) {\n p.cancel('Install cancelled.');\n return 130;\n }\n canonicalAgents = agentResult as string[];\n } else {\n canonicalAgents = [];\n }\n\n // 3. Agent channels (default = NONE — user explicitly picks)\n if (opts.agents) {\n targets = opts.agents;\n } else {\n const targetResult = await p.multiselect({\n message:\n 'Agent channels · space = 토글 · enter = 확인 · 최소 1개 선택',\n options: [\n {\n value: 'claude-code',\n label: 'Claude Code',\n hint: actuallyDetected.includes('claude-code') ? '.claude/ detected' : '',\n },\n {\n value: 'codex',\n label: 'Codex',\n hint: actuallyDetected.includes('codex') ? '.codex/ detected' : '',\n },\n {\n value: 'opencode',\n label: 'OpenCode',\n hint: actuallyDetected.includes('opencode') ? '.opencode/ detected' : '',\n },\n ] as { value: SkillTarget; label: string; hint?: string }[],\n initialValues: [] as SkillTarget[],\n required: true,\n });\n if (p.isCancel(targetResult)) {\n p.cancel('Install cancelled.');\n return 130;\n }\n targets = targetResult as SkillTarget[];\n }\n }\n\n const plans = targets.map((t) => planForTarget(projectRoot, t));\n\n p.log.message(\n pc.bold(`Skills (${skills.length}): `) +\n skills.map((s) => pc.cyan(s)).join(', ')\n );\n if (canonicalAgents.length > 0) {\n p.log.message(\n pc.bold(`Agents (${canonicalAgents.length}): `) +\n canonicalAgents.map((a) => pc.cyan(a.replace(/\\.md$/, ''))).join(', ')\n );\n }\n p.log.message(\n pc.bold('Targets: ') + targets.map((t) => pc.cyan(t)).join(', ')\n );\n\n const results: InstallResult[] = [];\n for (const plan of plans) {\n for (const skill of skills) {\n results.push(installOne(packageRoot, plan, skill, force));\n }\n }\n\n // Generate per-channel sub-agent definitions from the canonical `agents/`.\n // This is the v2 portable source-of-truth pattern (oh-my-agent style).\n // `canonicalAgents` is already resolved above by the TUI / --agents filter.\n for (const target of targets) {\n if (target === 'claude-code') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, projectRoot, 'claude', filename, force));\n }\n } else if (target === 'codex') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, projectRoot, 'codex', filename, force));\n }\n }\n // OpenCode currently has no agent-definition channel — skills only.\n }\n\n // Ship the read-only data assets (reference fingerprints, controlled vocab,\n // human-readable tag matrix, opt-out corpus) into the project so skills + hooks\n // can run entirely on the host CLI's own model — no external API keys.\n const dataFiles = [\n 'reference-fingerprints.json',\n 'reference-tags.md',\n 'vocabulary.json',\n 'synonyms.json',\n 'opt-out-corpus.json',\n ];\n for (const target of targets) {\n if (target === 'claude-code') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, projectRoot, '.claude', dataFile, force));\n }\n } else if (target === 'codex') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, projectRoot, '.codex', dataFile, force));\n }\n }\n }\n\n // Install hooks (Claude Code only — Codex / OpenCode have separate hook systems)\n if (targets.includes('claude-code')) {\n for (const hookFile of [\n 'skill-activation.cjs',\n 'session-state-loader.cjs',\n 'post-edit-watch.cjs',\n 'session-end-foldin.cjs',\n ]) {\n results.push(installHookFile(packageRoot, projectRoot, hookFile, force));\n }\n // settings.json (with merge, never clobber user)\n results.push(installSettingsJson(packageRoot, projectRoot, force));\n }\n\n p.log.message(pc.bold('\\nResults:'));\n for (const r of results) {\n const rel = relative(projectRoot, r.destPath);\n p.log.message(\n ` ${STATUS_LABEL[r.status]} ${pc.dim(r.target.padEnd(12))} ${rel}`\n );\n }\n\n const driftCount = results.filter((r) => r.status === 'skipped-drift').length;\n const writtenCount = results.filter(\n (r) => r.status === 'created' || r.status === 'updated'\n ).length;\n\n if (driftCount > 0) {\n p.outro(\n pc.yellow(\n `${writtenCount} written, ${driftCount} skipped (existing files lack the omd marker — rerun with --force to overwrite).`\n )\n );\n return 0;\n }\n\n // Friendly next-step nudge after successful install.\n // The first prompt is kept identical to the README's \"Your first 60 seconds\"\n // block so the README, the terminal, and the postinstall message all teach\n // the same activation moment. Bilingual (EN + KR) so an English reader is not\n // handed a Korean-only outro.\n const nextSteps = [\n `${pc.bold('Restart your agent, then type your first prompt:')}`,\n '',\n ` ${pc.cyan('EN')} ${pc.dim('Set up our design system — Toss-style, for a family meal-tracking app.')}`,\n ` ${pc.cyan('KR')} ${pc.dim('토스 스타일로 가족 식단 공유 앱 디자인 시스템 잡아줘')}`,\n '',\n `${pc.dim('Your agent runs omd:init and writes DESIGN.md. Then build against it:')}`,\n ` ${pc.cyan('EN')} ${pc.dim('Design the home screen.')} ${pc.cyan('KR')} ${pc.dim('홈 화면 디자인해줘')}`,\n '',\n `${pc.dim('Full walkthrough → \"Your first 60 seconds\" in the README. Routing is automatic — no slash command needed.')}`,\n `${pc.dim('Power user: ')}${pc.cyan('/omd-harness <task>')}${pc.dim(' — jump straight into the pipeline.')}`,\n '',\n `${pc.yellow('⚠ Already-running session?')} ${pc.dim('Run `/agents` to reload — or quit (Cmd+Q on macOS) and relaunch. Without reload, hooks/agents do not load.')}`,\n ].join('\\n');\n p.note(nextSteps, 'Next');\n\n // Counts derived from what was actually resolved/installed — never hardcoded,\n // so the outro can't drift from the real skill/agent/hook set (or the README).\n const hookCount = targets.includes('claude-code') ? 4 : 0;\n p.outro(\n pc.green(\n `Done. ${skills.length} skills · ${canonicalAgents.length} sub-agents · ${hookCount} hooks installed (${writtenCount} files).`,\n ),\n );\n return 0;\n}\n\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type AgentId = 'claude-code' | 'codex' | 'opencode' | 'cursor' | 'unknown';\n\nexport function detectCallingAgent(): AgentId {\n const env = process.env;\n\n if (env.CLAUDECODE === '1' || env.CLAUDE_CODE === '1' || env.CLAUDE_CODE_TASK_ID) {\n return 'claude-code';\n }\n if (env.CODEX_SESSION_ID || env.CODEX || env.OPENAI_CODEX) {\n return 'codex';\n }\n if (env.OPENCODE || env.OPENCODE_SESSION) {\n return 'opencode';\n }\n if (env.CURSOR_SESSION_ID || env.CURSOR_AGENT) {\n return 'cursor';\n }\n\n return 'unknown';\n}\n\nexport interface AgentPresence {\n claudeCode: boolean;\n codex: boolean;\n opencode: boolean;\n cursor: boolean;\n}\n\nexport function detectInstalledAgents(projectRoot: string): AgentPresence {\n return {\n claudeCode:\n existsSync(join(projectRoot, '.claude')) ||\n existsSync(join(projectRoot, 'CLAUDE.md')),\n codex:\n existsSync(join(projectRoot, '.codex')) ||\n existsSync(join(projectRoot, 'AGENTS.md')) ||\n existsSync(join(projectRoot, 'AGENTS.override.md')),\n opencode:\n existsSync(join(projectRoot, '.opencode')) ||\n existsSync(join(projectRoot, 'opencode.json')) ||\n existsSync(join(projectRoot, 'opencode.jsonc')),\n cursor:\n existsSync(join(projectRoot, '.cursor')) ||\n existsSync(join(projectRoot, '.cursorrules')),\n };\n}\n"],"mappings":"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","names":["existsSync","join","existsSync","join"]}