oh-my-design-cli 1.7.1 → 1.8.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/.claude/hooks/lib/preferences-parser.cjs +91 -0
- package/.claude/hooks/lib/preferences-writer.cjs +118 -0
- package/.claude/hooks/post-edit-watch.cjs +232 -36
- package/.claude/hooks/session-end-foldin.cjs +72 -19
- package/.claude/hooks/session-state-loader.cjs +52 -3
- package/.claude/hooks/skill-activation.cjs +40 -7
- package/README.ja.md +32 -103
- package/README.ko.md +45 -206
- package/README.md +33 -151
- package/README.zh-TW.md +32 -103
- package/agents/AGENT.md +3 -3
- package/agents/omd-master.md +16 -7
- package/agents/omd-microcopy.md +1 -1
- package/agents/omd-ux-engineer.md +9 -7
- package/agents/omd-ux-researcher.md +1 -1
- package/agents/omd-ux-writer.md +1 -1
- package/dist/bin/oh-my-design.js +3 -3
- package/dist/bin/oh-my-design.js.map +1 -1
- package/dist/{install-skills-YYHEC4CS.js → install-skills-7UUDOLG2.js} +152 -20
- package/dist/install-skills-7UUDOLG2.js.map +1 -0
- package/package.json +3 -1
- package/skills/omd-designer-review/SKILL.md +34 -0
- package/skills/omd-final-qa/SKILL.md +29 -0
- package/skills/omd-harness/SKILL.md +30 -9
- package/skills/omd-init/SKILL.md +52 -6
- package/skills/omd-kr-writer/SKILL.md +73 -3
- package/skills/omd-learn/SKILL.md +20 -0
- package/skills/omd-reference-capture/SKILL.md +15 -4
- package/skills/omd-taste/SKILL.md +79 -0
- package/dist/install-skills-YYHEC4CS.js.map +0 -1
|
@@ -39,8 +39,9 @@ shape: "[도메인] + [톤/스타일] + [핵심 화면]" — 예: "토스 스타
|
|
|
39
39
|
### 1.2 — 패키지 source 위치 확인 + 복구 Write
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# 패키지 dir resolution: local node_modules 우선 → global → (npx는 둘 다 없을 수 있음)
|
|
43
|
+
OMD_DIR=$(npm root)/oh-my-design-cli
|
|
44
|
+
[ -d "$OMD_DIR" ] || OMD_DIR=$(npm root -g)/oh-my-design-cli
|
|
44
45
|
SRC="$OMD_DIR/agents/omd-master.md"
|
|
45
46
|
TARGET=".claude/agents/omd-master.md" # (또는 detect한 폴더)
|
|
46
47
|
[ -f "$SRC" ] && cp "$SRC" "$TARGET" && echo "RECOVERED" || echo "SRC_MISSING"
|
|
@@ -54,13 +55,16 @@ omd-master subagent를 ${TARGET}에 복구했어요. /agents 한 번 실행하
|
|
|
54
55
|
|
|
55
56
|
### 1.3 — 복구도 실패한 경우 (SRC_MISSING / cp 권한 실패)
|
|
56
57
|
|
|
58
|
+
`SRC_MISSING`은 보통 npx 설치라 패키지 dir(`node_modules/oh-my-design-cli`)이 프로젝트에 없을 때 난다. 이 경우 `cp`로 복구할 source가 없는 게 정상 — 단, **installer가 이미 `install-skills` 시점에 `.claude/agents/omd-master.md`를 직접 생성**하므로, 먼저 `[ -f .claude/agents/omd-master.md ]`를 확인하라. 존재하면 복구 불필요(이미 설치됨) → 그대로 진행. 없을 때만 아래 안내.
|
|
59
|
+
|
|
57
60
|
기존 안내 fallback:
|
|
58
61
|
|
|
59
62
|
```
|
|
60
|
-
omd-master subagent
|
|
63
|
+
omd-master subagent를 찾을 수 없어요 (.claude/agents/omd-master.md 없음 + 패키지 source 누락 — npx 설치 가능성).
|
|
61
64
|
|
|
62
65
|
해결 (가장 빠른 순서):
|
|
63
66
|
1. `npx oh-my-design-cli@latest install-skills --all` 재실행 → /omd-harness 재호출
|
|
67
|
+
(install-skills가 .claude/agents/ + .claude/data/ 를 채워줍니다)
|
|
64
68
|
2. /agents 실행 → omd-* 목록 확인 → 안 보이면 Claude Code 재시작
|
|
65
69
|
```
|
|
66
70
|
|
|
@@ -158,17 +162,25 @@ reference를 고르라고 사용자에게 묻기 전에 **먼저 레포를 본
|
|
|
158
162
|
### 2.5.1 — ctx-prime helper 실행
|
|
159
163
|
|
|
160
164
|
```bash
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
# HELPER resolution (먼저 존재하는 것 사용):
|
|
166
|
+
# 1. .claude/data/scripts/ctx-prime.cjs ← installer가 복사 (npx 설치 기본 경로, 패키지 dir 불필요)
|
|
167
|
+
# 2. node_modules/oh-my-design-cli/scripts/ctx-prime.cjs (로컬 npm 설치)
|
|
168
|
+
# 3. $(npm root -g)/oh-my-design-cli/scripts/ctx-prime.cjs (global)
|
|
169
|
+
HELPER=".claude/data/scripts/ctx-prime.cjs"
|
|
170
|
+
if [ ! -f "$HELPER" ]; then
|
|
171
|
+
OMD_DIR=$(npm root)/oh-my-design-cli
|
|
172
|
+
[ -d "$OMD_DIR" ] || OMD_DIR=$(npm root -g)/oh-my-design-cli
|
|
173
|
+
HELPER="$OMD_DIR/scripts/ctx-prime.cjs"
|
|
174
|
+
fi
|
|
164
175
|
[ -f "$HELPER" ] || { echo "CTX_PRIME_MISSING"; exit 0; }
|
|
165
176
|
|
|
166
177
|
node "$HELPER" "$(pwd)" "${RUN_DIR}"
|
|
167
178
|
```
|
|
168
179
|
|
|
169
180
|
성공 시 `${RUN_DIR}/ctx-prime.json` 생성. ~12-50ms (typical repo).
|
|
181
|
+
ctx-prime.cjs는 `.claude/data/scripts/`에 installer가 복사하므로 패키지 dir 없이도 동작한다 (companion `context.cjs`도 같은 폴더에 함께 복사됨).
|
|
170
182
|
|
|
171
|
-
`CTX_PRIME_MISSING` (
|
|
183
|
+
`CTX_PRIME_MISSING` (세 경로 모두 miss) → Step 3로 직진 (legacy path).
|
|
172
184
|
|
|
173
185
|
### 2.5.2 — ctx-prime.json Read + 사용자 picker 게이트
|
|
174
186
|
|
|
@@ -189,7 +201,7 @@ options: ctx-prime.audience_hypothesis 상위 3개 → label/description 매핑
|
|
|
189
201
|
- audience_hypothesis[2]: label, description = evidence (없으면 생략)
|
|
190
202
|
```
|
|
191
203
|
|
|
192
|
-
(AskUserQuestion이 자동 "Other" 추가하므로 자유 입력 페르소나도 가능.)
|
|
204
|
+
(AskUserQuestion이 자동 "Other" 추가하므로 자유 입력 페르소나도 가능. Codex / OpenCode 등 AskUserQuestion이 없는 채널은 같은 question + option을 prose로 묻고 자유 텍스트 답을 받는다 — #21.)
|
|
193
205
|
|
|
194
206
|
사용자 답을 `ctx-prime.json`에 `confirmed_audience` 필드로 merge (Edit 또는 Write):
|
|
195
207
|
|
|
@@ -204,6 +216,8 @@ options: ctx-prime.audience_hypothesis 상위 3개 → label/description 매핑
|
|
|
204
216
|
|
|
205
217
|
페르소나 확정 직후 **AskUserQuestion 1번 더, 최대 4개 question 묶음**. ctx-prime 결과를 활용해 picker option을 동적 구성:
|
|
206
218
|
|
|
219
|
+
> **채널 분기 (#21)**: Claude Code 채널에서는 반드시 AskUserQuestion 툴로 제시 — 복수 답이 자연스러운 question(예: wow moment 여러 개 허용 시)은 `multiSelect: true`. Codex / OpenCode 등 툴이 없는 채널은 같은 question 묶음을 prose 1회 배치로 묻고 자유 텍스트 답을 받는다. 어느 채널이든 question 수 budget은 동일(아래 최대 4개) — 추가 게이트 금지.
|
|
220
|
+
|
|
207
221
|
**Question 1 — exit_scope:**
|
|
208
222
|
- "단일 화면만 (한 surface 깊이)"
|
|
209
223
|
- "풀 랜딩 (hero + features + CTA + footer)" — 추천 (대부분의 경우)
|
|
@@ -557,7 +571,14 @@ master에게 전달되는 prompt 첫 단락은 **반드시** 다음을 포함:
|
|
|
557
571
|
|
|
558
572
|
### 선택 절차
|
|
559
573
|
|
|
560
|
-
1. brand
|
|
574
|
+
1. brand reference DESIGN.md의 §1 (Visual Theme) + fingerprint `category`를 읽고 위 표에서 1순위 archetype 선택. reference DESIGN.md는 다음 우선순위로 resolve (omd:init Phase 4.1과 동일한 카탈로그 resolution order):
|
|
575
|
+
|
|
576
|
+
<!-- omd:catalog-resolution-order — omd-init/omd-reference-capture SKILL.md + agents/omd-master.md 와 동일 순서 강제. drift guard: test/unit/core/catalog-resolution-order.test.ts -->
|
|
577
|
+
|
|
578
|
+
1. `.claude/data/references/<id>/DESIGN.md` (installer가 복사 — npx 설치 기본 경로)
|
|
579
|
+
2. `node_modules/oh-my-design-cli/web/references/<id>/DESIGN.md` (로컬 npm 설치 직접 경로)
|
|
580
|
+
3. `web/references/<id>/DESIGN.md` (개발 레포)
|
|
581
|
+
4. `https://oh-my-design.kr/design-systems/<id>.md` 를 fetch (WebFetch 또는 `curl -fsSL`) — 200이면 본문이 곧 reference DESIGN.md. 가져온 내용을 `.claude/data/references/<id>/DESIGN.md`로 캐시해 다음부터는 로컬 캐시(경로 1)로 잡히게 한다.
|
|
561
582
|
2. 사용자가 같은 brand로 **이미 한 번 실험**했으면 (`.omd/runs/INDEX.md`에 기록) → 2순위 사용해서 variation 제공
|
|
562
583
|
3. 사용자가 명시 ("center 정렬로", "carousel로") → 그대로 따름
|
|
563
584
|
4. 선택된 archetype을 `experiment-meta.json`의 `hero_archetype` 필드에 명시 (gallery 표시용)
|
package/skills/omd-init/SKILL.md
CHANGED
|
@@ -15,6 +15,7 @@ description: "프로젝트 루트에 DESIGN.md를 부트스트랩 — 실제 기
|
|
|
15
15
|
Phase 1: 사용자 맥락 파악 (1-2 질문)
|
|
16
16
|
Phase 2: 레퍼런스 추천 (fingerprint 기반 in-head 점수)
|
|
17
17
|
Phase 3: 사용자가 1개 선택
|
|
18
|
+
Phase 3.5: 적용 형태 확인 (루트 부트스트랩 / 참고용 저장 / 기존 파일 교체 여부)
|
|
18
19
|
Phase 4: 레퍼런스 DESIGN.md Read + delta_set 추출
|
|
19
20
|
Phase 4.5: Philosophy Layer 입력 수집
|
|
20
21
|
Phase 5: Hybrid variation으로 DESIGN.md 작성
|
|
@@ -77,6 +78,17 @@ prose로:
|
|
|
77
78
|
|
|
78
79
|
vocab axis conflict 있으면 (예: formal ↔ playful) 먼저 알리고 우선시할 축을 묻기.
|
|
79
80
|
|
|
81
|
+
**Claude Code 채널이면 위 prose 대신 AskUserQuestion 툴 1개로 제시** (사용자가 화살표로 고르는 selectable UI — #21). question 1개에 top-5 후보 id 5개를 option으로:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
question: ""<task 핵심 한 줄>"에 맞는 레퍼런스를 골라주세요"
|
|
85
|
+
header: "Reference"
|
|
86
|
+
options: top1~top5 각각 → label = <id>, description = fingerprint 기반 1줄 (<category> · <tone_keywords 1-2개>)
|
|
87
|
+
- top1에는 label에 "(추천)" 표시
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
(AskUserQuestion이 자동 "Other"를 추가하므로 카탈로그의 다른 id를 자유 입력으로 답하는 것도 그대로 가능.) Codex / OpenCode 등 AskUserQuestion이 없는 채널은 위 prose 포맷 유지. 점수 계산·채택 로직(Phase 3)은 어느 쪽이든 동일.
|
|
91
|
+
|
|
80
92
|
## Phase 3 — 사용자 선택
|
|
81
93
|
|
|
82
94
|
- `go` 또는 top-5 안 id → 그 id 채택
|
|
@@ -84,21 +96,51 @@ vocab axis conflict 있으면 (예: formal ↔ playful) 먼저 알리고 우선
|
|
|
84
96
|
- 카탈로그에 없는 id → "해당 id는 카탈로그에 없어요. top-5 중에서 골라주세요."
|
|
85
97
|
- "중단" → 종료
|
|
86
98
|
|
|
99
|
+
## Phase 3.5 — 적용 형태 확인
|
|
100
|
+
|
|
101
|
+
레퍼런스 확정 직후, 어떻게 적용할지 묻는다. 먼저 프로젝트 루트에 `DESIGN.md`가
|
|
102
|
+
이미 있는지 확인하고, **Claude Code 채널이면 AskUserQuestion 1개** (타 채널은
|
|
103
|
+
prose)로:
|
|
104
|
+
|
|
105
|
+
- 루트에 DESIGN.md **없음**:
|
|
106
|
+
1. **프로젝트 디자인 시스템으로 설정 (추천)** — `<id>` 톤을 보존한 변형본을
|
|
107
|
+
루트 `DESIGN.md`로 부트스트랩 (Phase 4~7 전체 진행)
|
|
108
|
+
2. **참고용으로만 저장** — 루트 변경 없음. `<id>` 원본을
|
|
109
|
+
`design-references/<id>.md`로 복사하고 종료. "나중에 프로젝트에 적용하려면
|
|
110
|
+
다시 omd:init을 불러주세요" 안내. (변형 생성·셤 설치 안 함)
|
|
111
|
+
- 루트에 DESIGN.md **이미 있음** (option description에 그 파일의 §0/§1 요약
|
|
112
|
+
한 줄을 보여줄 것):
|
|
113
|
+
1. **교체 (추천)** — 기존 파일은 `DESIGN_DEPRECATED.md`로 보존됨을 명시
|
|
114
|
+
2. **참고용으로만 저장** — 위와 동일
|
|
115
|
+
3. **중단**
|
|
116
|
+
|
|
117
|
+
②(참고용) 선택 시: Phase 4.1로 원문만 확보해 `design-references/<id>.md`에
|
|
118
|
+
attribution 1줄(소스 URL + 날짜)과 함께 저장 → Phase 7 요약만 출력하고 종료.
|
|
119
|
+
Phase 4.2~6은 건너뛴다.
|
|
120
|
+
|
|
87
121
|
## Phase 4 — 레퍼런스 DESIGN.md 로드
|
|
88
122
|
|
|
89
123
|
### 4.1 경로 결정
|
|
90
124
|
|
|
91
|
-
선택된 id를 `<id>`로 하고, 다음 순서로 Read:
|
|
125
|
+
선택된 id를 `<id>`로 하고, 다음 순서로 Read (먼저 존재하는 것 사용):
|
|
92
126
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
127
|
+
<!-- omd:catalog-resolution-order — omd-harness/omd-reference-capture SKILL.md + agents/omd-master.md 와 동일 순서 강제. drift guard: test/unit/core/catalog-resolution-order.test.ts -->
|
|
128
|
+
|
|
129
|
+
1. `.claude/data/references/<id>/DESIGN.md` (installer가 복사 — npx 설치 기본 경로)
|
|
130
|
+
2. `node_modules/oh-my-design-cli/web/references/<id>/DESIGN.md` (로컬 npm 설치 직접 경로)
|
|
131
|
+
3. `web/references/<id>/DESIGN.md` (개발 레포)
|
|
132
|
+
4. `https://oh-my-design.kr/design-systems/<id>.md` 를 fetch (WebFetch 또는 `curl -fsSL`) — 1~3 로컬 경로가 전부 없을 때 (npx 설치가 기본 경로라 흔한 상황). 200이면 응답 본문이 곧 reference DESIGN.md다. 가져온 내용을 `.claude/data/references/<id>/DESIGN.md`로 저장해 다음 실행부터는 로컬 캐시(경로 1)로 잡히게 한다.
|
|
133
|
+
|
|
134
|
+
4까지 전부 실패하면 **절대 DESIGN.md를 임의로 지어내지 말 것.** 사용자에게
|
|
135
|
+
"레퍼런스 `<id>` 원문을 찾지 못했어요 (오프라인이거나 카탈로그 미배포).
|
|
136
|
+
네트워크 연결 후 재시도하거나 다른 레퍼런스를 골라주세요"라고 보고하고 종료.
|
|
96
137
|
|
|
97
138
|
전체 내용을 `reference_md` 변수로 보관 (Phase 5의 입력).
|
|
98
139
|
|
|
99
140
|
### 4.2 기존 DESIGN.md 처리
|
|
100
141
|
|
|
101
|
-
프로젝트 루트에 이미 `DESIGN.md`가
|
|
142
|
+
프로젝트 루트에 이미 `DESIGN.md`가 있으면 (Phase 3.5에서 이미 "교체"를 확인받은
|
|
143
|
+
상태 — 여기서 다시 묻지 않는다):
|
|
102
144
|
1. Read로 내용 확인
|
|
103
145
|
2. Bash로 `mv DESIGN.md DESIGN_DEPRECATED.md`
|
|
104
146
|
3. `DESIGN_DEPRECATED.md` 최상단에 다음 헤더 prepend (Edit 툴):
|
|
@@ -128,6 +170,10 @@ vocab axis conflict 있으면 (예: formal ↔ playful) 먼저 알리고 우선
|
|
|
128
170
|
}
|
|
129
171
|
```
|
|
130
172
|
|
|
173
|
+
빌더(oh-my-design.kr/builder)발 프롬프트에는 "Components: button, input, ..." 목록과
|
|
174
|
+
"(builder config: <URL>)"이 붙어올 수 있다. 컴포넌트 목록은 `"requested_components": ["button", "input", ...]`
|
|
175
|
+
키로 함께 기록하고, builder URL은 출처 표기용으로만 보존 — fetch하지 말 것.
|
|
176
|
+
|
|
131
177
|
`mode` 값 결정:
|
|
132
178
|
- omd:reference-capture가 먼저 돌았으면 그 결과 사용 (`.omd/init-context.json` 기존값)
|
|
133
179
|
- 아직 정해지지 않았으면 사용자에게 묻기 (omd:reference-capture Phase 0과 동일한 prompt). 라이브 캡쳐 없이 omd:init만 단독으로 도는 경우는 사실상 inspired 외엔 의미 없으므로 기본 `inspired`.
|
|
@@ -224,7 +270,7 @@ args: --force
|
|
|
224
270
|
|
|
225
271
|
omd:sync skill이 `CLAUDE.md`, `AGENTS.md`, `.cursor/rules/omd-design.mdc` shim 3종을 생성/갱신하고 `.omd/sync.lock.json` 업데이트.
|
|
226
272
|
|
|
227
|
-
**옵션 B**: omd:sync skill을 호출할 수 없는 환경이면 직접 Write로 shim 작성. 템플릿은 `skills/omd-sync/SKILL.md` §"shim body" 참조. 최소한:
|
|
273
|
+
**옵션 B**: omd:sync skill을 호출할 수 없는 환경이면 직접 Write로 shim 작성. 템플릿은 `.claude/skills/omd-sync/SKILL.md` (설치 경로; dev 레포에서는 `skills/omd-sync/SKILL.md`) §"shim body" 참조. 최소한:
|
|
228
274
|
- `CLAUDE.md`: managed block ("<!-- omd:managed:start --> ... <!-- omd:managed:end -->") 안에 DESIGN.md 참조 + 핵심 token 요약
|
|
229
275
|
- `AGENTS.md`: 동일 패턴
|
|
230
276
|
- `.cursor/rules/omd-design.mdc`: 전체 파일 omd 전용 — frontmatter + DESIGN.md 인용
|
|
@@ -20,7 +20,7 @@ inputs:
|
|
|
20
20
|
...
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
지원 preset 12개.
|
|
23
|
+
지원 preset 12개. 각 preset의 핵심 spec은 아래 **§9 (preset 요약 spec)** 에 self-contained로 수록 — 이 SKILL.md만으로 12개 preset 전부 실행 가능. (풀 9-field spec + verbatim 한국어 examples + 출처는 dev 레포의 `data/research/2026-05-18-kr-style-presets.md` — npx 배포본에는 미포함.)
|
|
24
24
|
|
|
25
25
|
| # | preset_id | 종결 | 분량 (한글) | 용도 |
|
|
26
26
|
|---|---|---|---|---|
|
|
@@ -39,11 +39,11 @@ inputs:
|
|
|
39
39
|
|
|
40
40
|
**호출 시 preset 미지정 → `toss-tech-design` 적용.**
|
|
41
41
|
|
|
42
|
-
본 문서의 § 1~8은 default preset (`toss-tech-design`)의 상세 spec. 다른 preset 선택 시
|
|
42
|
+
본 문서의 § 1~8은 default preset (`toss-tech-design`)의 상세 spec. 다른 preset 선택 시 §9의 해당 preset 요약 spec 룰로 substitution.
|
|
43
43
|
|
|
44
44
|
### 전환 매트릭스 (자주 쓰는 6개)
|
|
45
45
|
|
|
46
|
-
같은 콘텐츠를 다른 preset으로 옮길 때 standard rewrite
|
|
46
|
+
같은 콘텐츠를 다른 preset으로 옮길 때 standard rewrite 룰 — 아래 핵심 변환 + §9 spec만으로 수행 가능. (확장 5-step rule은 dev 레포 research doc §14 — 배포본 미포함.)
|
|
47
47
|
|
|
48
48
|
| from | to | 핵심 변환 |
|
|
49
49
|
|---|---|---|
|
|
@@ -224,6 +224,76 @@ Toss 글은 단락 2~3개 당 이미지 1개. oh-my-design 본문에서:
|
|
|
224
224
|
- 첫 단락 "안녕하세요" 인사 포함
|
|
225
225
|
- 가져가도 좋은 것 / 따라하면 안 되는 것 = 각 5개
|
|
226
226
|
|
|
227
|
+
## 9. Preset 요약 spec (non-default 11개)
|
|
228
|
+
|
|
229
|
+
toss-tech-design 외 preset 호출 시 아래 요약 spec이 §1 (voice) / §5 (안티패턴) / §6 (어휘) 룰을 대체한다. 분량은 §0 표의 분량 컬럼, 구조(§2~4)는 preset 성격에 맞게 준용.
|
|
230
|
+
|
|
231
|
+
### karrot-neighborly — 당근 동네체
|
|
232
|
+
- register: 해요체 (가끔 "~답니다"/"~지요") · 문장 25~45자
|
|
233
|
+
- opening: "안녕하세요, [동네] 이웃 여러분." / closing: "오늘도 따뜻한 하루 보내세요."
|
|
234
|
+
- 금지: 차가운 비즈니스 한자어("고객", "유저") · 외래어 남발("커뮤니티"보단 "동네") · 단정적 -다 종결
|
|
235
|
+
- 선호: "동네/이웃/우리/근처" lexicon · 다정한 호명("이웃님") · 골목·계절·온도가 묻어나는 묘사어
|
|
236
|
+
|
|
237
|
+
### brunch-maker-popular — 브런치 에세이체
|
|
238
|
+
- register: 해요체 80% + 회상 해체 혼용 (1~2문단당 한 번 "-했다") · 문장 40~80자, 시처럼 끊기는 호흡
|
|
239
|
+
- opening: 개인 일화 한 줄 ("그날 저녁, 나는 ~") / closing: 여운 남기는 단문 ("그래서 나는 오늘도 ~.")
|
|
240
|
+
- 금지: 보고서식 bullet · 차트 인용 · 단정적 톤("~해야 한다")
|
|
241
|
+
- 선호: 1인칭 "나"의 일화 · 감각어(냄새·빛·소리) · 단문/만연체 교차 · 은유 · 짧은 의문문("정말 그럴까?")
|
|
242
|
+
|
|
243
|
+
### naver-d2-engineering — NAVER D2 기술체
|
|
244
|
+
- register: 하십시오체(-합니다/-입니다) · 문장 40~70자, 명확한 주어-서술 구조
|
|
245
|
+
- opening: "안녕하세요. [팀명] [이름]입니다. 이 글에서는 ~을 다룹니다." / closing: "이 글이 ~ 도움이 되길 바랍니다."
|
|
246
|
+
- 금지: 과도한 1인칭 감정 표현 · 비속어/인터넷 약어 · "~해요" 혼용 (문서 내 통일성)
|
|
247
|
+
- 선호: 정확한 기술 용어 + 영문 병기 · "먼저/다음으로/마지막으로" 구조 신호어 · 코드 블록·도식과 본문 1:1
|
|
248
|
+
|
|
249
|
+
### biz-formal-report — 보고서 사무체
|
|
250
|
+
- register: 하십시오체(-합니다) 또는 명사형 종결(-함/-임/-하였음) · 문장 30~55자, 짧고 단정
|
|
251
|
+
- opening: "1. 개요 / 본 보고서는 ~을 목적으로 작성되었습니다." / closing: "이상입니다." / "끝."
|
|
252
|
+
- 금지: 1인칭 "저/우리" 남발 (회사·부서 단위로 대체) · 감정 형용사 · 의문문 · 이모지/외래어 남용
|
|
253
|
+
- 선호: 섹션 번호(1.1, 1.2.1) · 명사형 종결("진행함", "확인됨") · 사무 한자어("검토/보고/회신/추진") · 결론→근거 역피라미드 · 핵심 수치 bold
|
|
254
|
+
|
|
255
|
+
### academic-paper — 학술 논문체
|
|
256
|
+
- register: 해라체(-한다/-이다) 평어체 · 문장 50~90자, 복문 빈번
|
|
257
|
+
- opening: "본 연구는 ~을 목적으로 한다." / closing: "후속 연구에서는 ~을 다룰 필요가 있다." / "본 연구의 한계는 ~이다."
|
|
258
|
+
- 금지: 비격식체 · 1인칭 단수("필자"·"본 연구자"로 대체) · 감정 형용사 · 인용 없는 단정
|
|
259
|
+
- 선호: 한자어 비중 높음("선행연구/유의수준/검정") · 수동·피동("측정되었다") · 괄호 인용(저자, 연도) · "따라서/그러나/한편" 접속어
|
|
260
|
+
|
|
261
|
+
### journalism-broadsheet — 신문 기사체
|
|
262
|
+
- register: 해라체(-다/-었다) · 문장 35~60자, 리드 문장 명확
|
|
263
|
+
- opening: 리드 = 핵심 사실 한 줄 ("~가 ~했다고 ~일 밝혔다.") / closing: "한편, ~." (배경 정보 마무리)
|
|
264
|
+
- 금지: 1인칭 (기명 칼럼 제외) · "~요/~네요" · 추측만 나열 · 감정 형용사
|
|
265
|
+
- 선호: 5W1H 리드 · 직접 인용("~라고 밝혔다/말했다/강조했다") · 출처 표기("OOO 대표") · "한편/이에 대해/앞서"
|
|
266
|
+
|
|
267
|
+
### kakao-warm-product — 카카오 따뜻한 프로덕트체
|
|
268
|
+
- register: 해요체, Toss보다 약간 더 감정적 · 문장 20~40자 (microcopy는 더 짧게)
|
|
269
|
+
- opening: "~ 축하해요!" / "~을(를) 시작해볼까요?" / closing: "오늘도 좋은 하루 보내세요." / "더 알아보기 →"
|
|
270
|
+
- 금지: 비즈니스 한자어("승인/수행/처리" → 부드러운 동사) · 부정·경고 직설("실패/오류" → "다시 시도해볼까요?") · 명령형("입력하시오" → "입력해주세요")
|
|
271
|
+
- 선호: "축하해요/고마워요/함께해요" · "혜택/선물/응원" 어휘 · 짧은 의문문("같이 시작해볼까요?")
|
|
272
|
+
|
|
273
|
+
### line-global-saas — LINE 한국어 (cross-locale)
|
|
274
|
+
- register: 하십시오체(-합니다), D2보다 한자어 비중 낮음 · 문장 35~55자, 번역 친화적 단순 구조
|
|
275
|
+
- opening: "이 글에서는 ~을 소개합니다." / "안녕하세요, LINE의 ~입니다." / closing: "감사합니다." / "다음 글에서 ~을 이어 다루겠습니다."
|
|
276
|
+
- 금지: 한국 내수 전용 비유·시사 (동시 번역 시 문제) · 4자 숙어("일석이조") · 비격식체
|
|
277
|
+
- 선호: SVO 단순 구조·능동태 · 영문 약어 병기("LINE 디자인 시스템(LDS)") · terminology 통일 · 중립 호명("독자/사용자")
|
|
278
|
+
|
|
279
|
+
### academic-lecture-essay — 교양 강연체
|
|
280
|
+
- register: 해요체 우세 + 학술 한자어 자유 혼용 · 문장 40~70자, 강의처럼 호흡 길게
|
|
281
|
+
- opening: "여러분, ~을 한 번 생각해볼까요?" / closing: "결국 ~인 거죠." / "그래서 저는 ~라고 생각해요."
|
|
282
|
+
- 금지: 보고서식 bullet · 인용 없는 단정
|
|
283
|
+
- 선호: "~인 거죠/~라는 거예요" · 청자에게 묻는 구조("그렇다면 왜 그럴까요?") · 비유와 일상 예시 · 학문 한자어 즉시 풀이("엔트로피, 그러니까 무질서의 정도")
|
|
284
|
+
|
|
285
|
+
### emotional-brand — 패션 커머스 트렌디체
|
|
286
|
+
- register: 해요체 + 체언/명사 종결 자유 혼용("오늘의 픽.") · 헤드라인 15~30자, 본문 40자 내외
|
|
287
|
+
- opening: 명사형 후크 ("이번 주의 무드." / "여름밤, 우리가 입어야 할 것.") / closing: "지금, [브랜드]에서." / "더 보기 →"
|
|
288
|
+
- 금지: 보고서 한자어 · "~합니다" 일변도 (브랜드 거리감) · 길고 설명적인 문장
|
|
289
|
+
- 선호: 외래어·영문 자유("무드/감도/베이직/에디트") · 컬러·재질·실루엣 어휘 · 시즌·시간성 cue("이번 주/오늘 밤/5월의") · 짧은 명령형("입어볼 것.")
|
|
290
|
+
|
|
291
|
+
### legal-disclosure — 법무 약관체
|
|
292
|
+
- register: 하십시오체(-합니다) 우세 + 명사 종결("~함") · 문장 50~100자, 의도적으로 길고 한정조건 빽빽
|
|
293
|
+
- opening: "본 약관은 ~을 규정함을 목적으로 합니다." / "제1조 (목적)" / closing: "본 약관은 ~일부터 시행합니다." / "부칙. ~"
|
|
294
|
+
- 금지: 1·2인칭 친밀 표현 · 비격식체 · 감정 형용사 · 모호한 수식("좀", "약간")
|
|
295
|
+
- 선호: "~합니다/~됩니다/~할 수 있습니다" · 수동·피동("간주됩니다/처리됩니다") · 정의 조항("이 약관에서 사용하는 용어의 정의는 다음과 같습니다.") · 법률 한자어("의무/책임/면책/준수") · 조·항·호 번호 체계
|
|
296
|
+
|
|
227
297
|
---
|
|
228
298
|
|
|
229
299
|
이 스킬은 oh-my-design 블로그 한국어 본문의 **단일 source of truth voice guide**. 모든 KR 글은 이 guide를 따른다.
|
|
@@ -83,6 +83,23 @@ spacing (1 pending):
|
|
|
83
83
|
Review .omd/preferences.md for details.
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
## Fold-in 제안에서 호출된 경우 (`.omd/foldin-proposal.json`)
|
|
87
|
+
|
|
88
|
+
SessionStart 컨텍스트의 OMD FOLD-IN PROPOSAL → AskUserQuestion 승인 경로로 호출되었으면 Phase 2 확인은 이미 끝난 것 — 다시 묻지 말 것.
|
|
89
|
+
|
|
90
|
+
제안 없이 사용자가 직접 omd:learn을 부른 경우에도 `.omd/foldin-proposal.json`이
|
|
91
|
+
`"status": "proposed"`로 존재하면: 그 scopes를 이번 폴드 대상에 포함할지 Phase 2에서
|
|
92
|
+
함께 확인하고, 처리 후 아래와 동일하게 status를 갱신한다 (proposed인 채로 방치 금지 —
|
|
93
|
+
다음 세션이 또 물어본다).
|
|
94
|
+
|
|
95
|
+
- **승인된 scope만** Phase 3-4로 처리. 미승인 scope의 pending 엔트리는 건드리지 않는다
|
|
96
|
+
- 처리 후 `.omd/foldin-proposal.json`의 status를 Edit 툴로 갱신:
|
|
97
|
+
- 전부 반영 → `"status": "applied"` + `"applied_at": "<ISO timestamp>"` 필드 추가
|
|
98
|
+
- 일부만 반영 → `"status": "partial"` + `scopes` 배열을 **남은(미승인) scope만**으로 갱신
|
|
99
|
+
- 전부 거절("나중에") → `"status": "snoozed"` + `"snoozed_at": "<ISO timestamp>"` 필드 추가
|
|
100
|
+
- status 값은 **JSON 계약상 영문 고정** (`proposed`/`applied`/`partial`/`snoozed`) —
|
|
101
|
+
번역·한글화 금지 (훅이 문자열 비교로 읽는다)
|
|
102
|
+
|
|
86
103
|
## 옵션 패턴
|
|
87
104
|
|
|
88
105
|
사용자가 특정 작업만 요청하는 경우:
|
|
@@ -91,6 +108,9 @@ Review .omd/preferences.md for details.
|
|
|
91
108
|
- **"X scope만 반영"** → 해당 scope만 Phase 3에서 처리
|
|
92
109
|
- **"<pref_id>를 applied로 표시"** → Phase 4의 single-entry 플립만
|
|
93
110
|
- **"<pref_id>를 rejected로 표시 + 이유"** → 동일
|
|
111
|
+
- 플립 전 현재 status를 먼저 Read로 확인: 이미 같은 값이면 no-op 보고,
|
|
112
|
+
`superseded`/`rejected` → `applied` 전환은 **금지** (이력 오염 — 사용자에게
|
|
113
|
+
"이 항목은 X 상태예요. 되살리려면 omd:remember로 재캡처하세요"라고 안내)
|
|
94
114
|
|
|
95
115
|
## 금지
|
|
96
116
|
|
|
@@ -102,11 +102,22 @@ id가 카탈로그에 없으면 종료 + "X는 reference 카탈로그에 없어
|
|
|
102
102
|
|
|
103
103
|
## Phase 2 — 라이브 URL 수집
|
|
104
104
|
|
|
105
|
+
**reference 자료 경로 `<refdir>`** 는 reference DESIGN.md 위치 기준으로 resolve (먼저 존재하는 것 사용 — omd:init Phase 4.1과 동일한 카탈로그 resolution order):
|
|
106
|
+
|
|
107
|
+
<!-- omd:catalog-resolution-order — omd-init/omd-harness SKILL.md + agents/omd-master.md 와 동일 순서 강제. drift guard: test/unit/core/catalog-resolution-order.test.ts -->
|
|
108
|
+
|
|
109
|
+
1. `.claude/data/references/<id>/DESIGN.md` (installer가 복사 — npx 설치 기본 경로; 디렉토리에는 **DESIGN.md만** 보장)
|
|
110
|
+
2. `node_modules/oh-my-design-cli/web/references/<id>/DESIGN.md` (로컬 npm 설치 직접 경로 — 디렉토리에 _promo.json/_research.md 포함)
|
|
111
|
+
3. `web/references/<id>/DESIGN.md` (개발 레포)
|
|
112
|
+
4. `https://oh-my-design.kr/design-systems/<id>.md` 를 fetch (WebFetch 또는 `curl -fsSL`) — 1~3 로컬 경로가 전부 없을 때. 200이면 본문이 곧 reference DESIGN.md. 가져온 내용을 `.claude/data/references/<id>/DESIGN.md`로 캐시해 다음부터는 로컬 캐시(경로 1)로 잡히게 한다.
|
|
113
|
+
|
|
114
|
+
`<refdir>` = resolve된 DESIGN.md가 있는 디렉토리 (tier 4로 fetch한 경우 캐시 후 `.claude/data/references/<id>/`). `_promo.json`/`_research.md`는 (1)/(4)에 없을 수 있으니, 없으면 (2)/(3)로 폴백하고 그래도 없으면 fingerprints 기반 추론으로 진행.
|
|
115
|
+
|
|
105
116
|
다음을 순서대로 시도:
|
|
106
117
|
|
|
107
118
|
1. **homepage URL**:
|
|
108
|
-
-
|
|
109
|
-
- 없으면
|
|
119
|
+
- `<refdir>/_promo.json`의 `logo_url`이 brand site면 거기서 도메인 추출
|
|
120
|
+
- 없으면 `<refdir>/_research.md`에서 Tier 1 source URL grep
|
|
110
121
|
- 둘 다 없으면 `.claude/data/reference-fingerprints.json`의 `items[].category_raw` 기반으로 추론 — 마지막 수단
|
|
111
122
|
|
|
112
123
|
2. **logo URL** (`_promo.json` 우선):
|
|
@@ -114,7 +125,7 @@ id가 카탈로그에 없으면 종료 + "X는 reference 카탈로그에 없어
|
|
|
114
125
|
- 없으면 homepage HTML에서 `apple-touch-icon` / `og:image` / favicon-256 추출
|
|
115
126
|
|
|
116
127
|
3. **공식 DS docs URL** (있으면):
|
|
117
|
-
-
|
|
128
|
+
- `<refdir>/DESIGN.md`의 footer 또는 §4 verified 섹션에서 grep
|
|
118
129
|
|
|
119
130
|
수집한 URL 후보를 사용자에게 보여주고 확인:
|
|
120
131
|
|
|
@@ -374,7 +385,7 @@ playwright MCP를 사용해 homepage에 navigate한 뒤 computed styles를 추
|
|
|
374
385
|
}
|
|
375
386
|
```
|
|
376
387
|
|
|
377
|
-
inspect
|
|
388
|
+
inspect 패턴: 5-10 elements 샘플링 (`body` / `header nav` / hero CTA / card / `footer` 류 대표 element의 getComputedStyle 추출 — Phase 3.9의 JS 패턴과 동일한 방식). 캡쳐하려는 brand가 카탈로그에 없으면 웹 카탈로그(`oh-my-design.kr/design-systems`)에서 먼저 찾아보고, 카탈로그에 새 brand를 직접 추가하는 워크플로우(omd-add-reference)는 dev 레포(github.com/kwakseongjae/oh-my-design)에만 존재 — 배포본(npx 설치)에는 포함되지 않는다.
|
|
378
389
|
|
|
379
390
|
## Phase 4.5 — 구조 cue 캡쳐 (structure.json)
|
|
380
391
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omd:taste
|
|
3
|
+
description: "시스템이 학습한 사용자의 디자인 취향을 한 뷰로 렌더. .omd/preferences.md + foldin-proposal.json + DESIGN.md를 읽어 반영됨/대기 중/보류됨/모르는 것 4그룹으로 보여준다. '/omd-taste', '내 취향 보여줘', '취향 현황', 'what are my preferences', 'show my taste', 「私の好みを見せて」, 「顯示我的偏好」류의 발화에 트리거. 기록은 omd:remember, DESIGN.md 반영은 omd:learn."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# omd:taste — Taste Dashboard
|
|
8
|
+
|
|
9
|
+
시스템이 사용자에 대해 알고 있는 취향을 **한 뷰**로 렌더한다. 신뢰는 가시성에서 나온다 — 루프가 뭘 배웠고, 뭘 기다리고, 뭘 모르는지 보여주면 사용자가 루프에 더 먹인다. 기본 동작은 **읽기 전용** — 기록은 `omd:remember`, 반영은 `omd:learn`의 몫이고, 이 스킬은 보여주고 다음 행동만 안내한다. **CLI 호출 없음** — Read/Edit 툴로 직접 처리.
|
|
10
|
+
|
|
11
|
+
## 입력 (모두 Read 툴)
|
|
12
|
+
|
|
13
|
+
1. `.omd/preferences.md` — canonical 포맷 (`## <heading>` + ` ```omd-meta` 블록 + body — `omd:remember` 스킬의 포맷 정의 참조). **모든 status**의 엔트리를 파싱: `id` / `timestamp` / `scope` / `signal` / `confidence` / `status`
|
|
14
|
+
2. `.omd/foldin-proposal.json` — 있으면 `status` (`proposed` / `applied` / `partial` / `snoozed`)와 `scopes[]` (scope, count, score, summary)
|
|
15
|
+
3. `DESIGN.md` — § 라우팅 표시 + "모르는 것" 축 도출용
|
|
16
|
+
|
|
17
|
+
## 빈 상태
|
|
18
|
+
|
|
19
|
+
`.omd/preferences.md`가 없거나 파싱된 엔트리가 0개면 두 줄로 끝낸다:
|
|
20
|
+
|
|
21
|
+
> 아직 학습된 취향이 없어요.
|
|
22
|
+
> 작업 중 "앞으로는 ~로 해"라고 말하면 omd:remember가 기록하고, 같은 취향이 쌓이면 omd:learn이 DESIGN.md에 정식 반영해요.
|
|
23
|
+
|
|
24
|
+
이때 `.omd/` 디렉토리나 파일을 **만들지 말 것** — 뷰 스킬이 상태를 생성하면 안 된다.
|
|
25
|
+
|
|
26
|
+
## 렌더 — 4 그룹 한 뷰
|
|
27
|
+
|
|
28
|
+
### ① 반영됨 (`status: applied`)
|
|
29
|
+
|
|
30
|
+
scope별 한 줄: body 요약 + 반영된 DESIGN.md 섹션 + `applied_at` (있으면). 섹션은 omd:learn Phase 3의 scope→§ 라우팅으로 도출:
|
|
31
|
+
|
|
32
|
+
| scope | DESIGN.md § |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `components.*` | §8 (Components) 또는 §13 |
|
|
35
|
+
| `color` | §2 (Color Palette) |
|
|
36
|
+
| `typography` | §3 |
|
|
37
|
+
| `spacing` | §4 |
|
|
38
|
+
| `voice` | §10 (Voice & Tone) |
|
|
39
|
+
| `motion` | §15 (Motion & Easing) |
|
|
40
|
+
| `visualTheme` | §1 (Visual Theme) |
|
|
41
|
+
|
|
42
|
+
DESIGN.md에 해당 섹션이 실제로 없으면 § 표기는 생략 (추측으로 적지 말 것).
|
|
43
|
+
|
|
44
|
+
### ② 대기 중 (`status: pending`)
|
|
45
|
+
|
|
46
|
+
scope별로 그룹화해 한 그룹당 한 줄:
|
|
47
|
+
|
|
48
|
+
- **발생 횟수** — 해당 scope의 pending 엔트리 수
|
|
49
|
+
- **confidence** — explicit / inferred 구성 (예: `explicit ×2, inferred ×1`)
|
|
50
|
+
- **한 줄 요약** — 최신 엔트리 body의 첫 줄
|
|
51
|
+
- **폴드 제안까지 남은 횟수** — 자동 fold-in 게이트(session-end hook)의 기본 임계는 **최근 7일 창 안에서 같은 scope 3회**. `max(0, 3 - 7일 내 발생 횟수)`로 계산해 "N회 더 쌓이면 제안돼요"로 표기. 이미 충족이면 "다음 세션 종료 시 제안 예정"
|
|
52
|
+
|
|
53
|
+
### ③ 보류됨 (snoozed)
|
|
54
|
+
|
|
55
|
+
`.omd/foldin-proposal.json`의 `status`가 `snoozed`면 그 `scopes[]`를 나열 — scope + summary + `snoozed_at`. "지금 반영하려면 omd:learn을 부르세요" 한 줄 덧붙임. proposal 파일이 없거나 snoozed가 아니면 이 그룹은 생략.
|
|
56
|
+
|
|
57
|
+
### ④ 시스템이 모르는 것 (가볍게)
|
|
58
|
+
|
|
59
|
+
DESIGN.md에는 정의돼 있지만 preference 엔트리(어느 status든)로 한 번도 확인된 적 없는 주요 축을 **1-2개만** 짚는다 (예: "motion은 DESIGN.md 기본값 그대로 — 아직 취향을 들은 적이 없어요"). 절대 전 축을 나열하지 말 것 — 가볍게 한두 줄.
|
|
60
|
+
|
|
61
|
+
## 다음 행동 안내 (뷰 끝에 항상)
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
다음 중 하나로 이어갈 수 있어요:
|
|
65
|
+
- "지금 반영" → omd:learn — pending을 DESIGN.md에 fold
|
|
66
|
+
- "잊어줘 <id|scope>" → 해당 엔트리 status를 rejected로 플립
|
|
67
|
+
- "수정: <내용>" → omd:remember로 재기록 (옛 엔트리는 superseded)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **"지금 반영"** → `omd:learn` 트리거 (전체 또는 사용자가 지정한 scope만)
|
|
71
|
+
- **"잊어줘"** → 해당 엔트리의 omd-meta 블록만 Edit 툴로 `status: rejected` + `rejected_reason: "user-forget"` 플립 (omd:learn의 single-entry 플립과 동일 절차). body는 건드리지 않는다
|
|
72
|
+
- **"수정"** → body 직접 편집 금지 (영구 기록) — `omd:remember`로 새 엔트리 재기록 후 옛 엔트리를 `status: superseded` + `superseded_by: <새 id>`로 플립
|
|
73
|
+
|
|
74
|
+
## 금지
|
|
75
|
+
|
|
76
|
+
- "잊어줘"/"수정" 후속 요청의 status 플립 외에는 어떤 파일도 수정하지 말 것 — DESIGN.md 수정은 항상 omd:learn 경유
|
|
77
|
+
- 엔트리 전체 dump 금지 — scope 그룹 요약으로
|
|
78
|
+
- ④번 그룹을 채우려고 억지 축을 만들지 말 것 — 짚을 게 없으면 생략
|
|
79
|
+
- preferences.md 포맷 해석은 canonical 규칙(omd:remember)만 따를 것 — 자체 변형 포맷 발명 금지
|
|
@@ -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 cpSync,\n} from 'node:fs';\nimport { join, dirname, relative } from 'node:path';\nimport { homedir } from 'node:os';\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 /** Minimal install: only the named skill files — skip sub-agents, data files,\n * hooks, and settings.json. Ideal for shipping a single standalone skill. */\n skillsOnly?: boolean;\n /** Install to the user-level dir (~/.claude/skills) instead of this project.\n * Writes skills + sub-agents (+ data); never touches global hooks/settings.\n * When unset and interactive, the TUI asks project-vs-global. */\n global?: boolean;\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 // Official Codex skill discovery path is `.agents/skills/<name>/SKILL.md`\n // (developers.openai.com/codex/skills) — NOT `.codex/skills`. Folder layout\n // so multi-file skills (scripts/, references/) install + run.\n return {\n target,\n destDir: join(projectRoot, '.agents', 'skills'),\n layout: 'folder',\n };\n case 'opencode':\n // OpenCode loads `.opencode/skills/<name>/SKILL.md` (opencode.ai/docs/skills)\n // as folder skills — the old flat `.opencode/agents/<name>.md` couldn't host\n // a skill's scripts/references.\n return {\n target,\n destDir: join(projectRoot, '.opencode', 'skills'),\n layout: 'folder',\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' | 'skipped-incompat';\n}\n\n// Skill-tree entries that must never be installed (runtime state, caches, OS cruft).\nconst IGNORED_SKILL_ENTRIES = new Set(['.runtime', '__pycache__', '.DS_Store']);\n\n/**\n * A skill may restrict itself to specific agent channels via a frontmatter line\n * `x-omd-channels: claude-code` (comma/space separated). Returns the allowed\n * channels, or null when channel-agnostic (installs anywhere). Used by skills that\n * depend on a particular agent runtime — e.g. claude-design needs Claude Code's\n * claude-in-chrome MCP + Bash/python/node and is therefore claude-code only.\n */\nfunction parseSkillChannels(skillMd: string): SkillTarget[] | null {\n const fm = /^---\\n([\\s\\S]*?)\\n---/.exec(skillMd);\n if (!fm) return null;\n const m = /^x-omd-channels:\\s*(.+)$/m.exec(fm[1]);\n if (!m) return null;\n const valid: SkillTarget[] = ['claude-code', 'codex', 'opencode'];\n const list = m[1]\n .split(/[,\\s]+/)\n .map((s) => s.trim())\n .filter((s): s is SkillTarget => (valid as string[]).includes(s));\n return list.length > 0 ? list : null;\n}\n\n/**\n * The agent channels a skill can install into: its declared `x-omd-channels`\n * (if any), else all channels. All three channels now use folder layout\n * (.claude/skills, .agents/skills, .opencode/skills) so multi-file skills with\n * scripts/references install everywhere — the only restriction is what the skill\n * itself declares (e.g. claude-design needs a browser-driving runtime).\n */\nfunction skillSupportedChannels(packageRoot: string, skill: string): SkillTarget[] {\n return (\n parseSkillChannels(readFileSync(join(packageRoot, 'skills', skill, 'SKILL.md'), 'utf8')) ??\n (['claude-code', 'codex', 'opencode'] as SkillTarget[])\n );\n}\n\nfunction installOne(\n packageRoot: string,\n plan: InstallPlan,\n skill: string,\n force: boolean\n): InstallResult {\n const skillDir = join(packageRoot, 'skills', skill);\n const src = readFileSync(join(skillDir, 'SKILL.md'), 'utf8');\n const managed = MANAGED_HEADER + '\\n\\n' + src;\n\n // Respect a skill's declared channel restriction (frontmatter `x-omd-channels:`).\n const channels = parseSkillChannels(src);\n if (channels && !channels.includes(plan.target)) {\n return {\n target: plan.target,\n skill,\n destPath: join(plan.destDir, skill + '.md'),\n status: 'skipped-incompat',\n };\n }\n\n // A skill is \"multi-file\" when it ships more than SKILL.md (scripts/, references/, …).\n const extras = readdirSync(skillDir).filter(\n (n) => n !== 'SKILL.md' && !IGNORED_SKILL_ENTRIES.has(n)\n );\n const isMultiFile = extras.length > 0;\n\n // Flat channels (codex/opencode) store a skill as a single <skill>.md and cannot\n // host a multi-file skill's scripts/references — such skills are claude-code only.\n if (plan.layout !== 'folder' && isMultiFile) {\n return {\n target: plan.target,\n skill,\n destPath: join(plan.destDir, skill + '.md'),\n status: 'skipped-incompat',\n };\n }\n\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 // Drift protection guards the user-editable SKILL.md. Single-file skills can\n // short-circuit on \"unchanged\"; multi-file skills always re-sync their tree.\n if (exists && existing === managed && !isMultiFile) {\n return { target: plan.target, skill, destPath, status: 'unchanged' };\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\n // Copy the rest of the skill tree (scripts/, references/, …) for folder layout.\n if (plan.layout === 'folder' && isMultiFile) {\n const destSkillDir = join(plan.destDir, skill);\n for (const entry of extras) {\n cpSync(join(skillDir, entry), join(destSkillDir, entry), {\n recursive: true,\n filter: (s) => !/(\\/__pycache__|\\/\\.runtime|\\.pyc$|\\.DS_Store$)/.test(s),\n });\n }\n }\n\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 'skipped-incompat': pc.yellow('skipped (claude-code only)'),\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 const minimal = opts.skillsOnly === true;\n // Install scope: 'project' (<cwd>/.claude/…) or 'global' (~/.claude/…). --global\n // forces it; otherwise the interactive TUI asks. Global writes skills + sub-agents\n // (+ data) to the user-level dir but never touches global hooks/settings.json.\n let scope: 'project' | 'global' = opts.global ? 'global' : 'project';\n\n p.intro(\n pc.bold('omd install-skills') +\n pc.dim(` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n\n // Each dimension (scope / skills / sub-agents / channels) is resolved\n // independently: a CLI flag pins it; otherwise we prompt — but only when stdin\n // is a TTY and --all wasn't passed. This is the key fix: `--skills X` or\n // `--skills-only` no longer suppress the *channel* (where to install) prompt —\n // they only pin the dimension they name.\n const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);\n const interactive = isTTY && !opts.all;\n\n const detected = autoDetectTargets(projectRoot);\n // Real presence (not the all-3 fallback) — used for hint labels + prompt defaults.\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 // --- Scope (project vs global) — --global pins it, else ask / default project.\n if (!opts.global && interactive) {\n const scopeResult = await p.select({\n message: 'Install scope · 어디에 설치할까요?',\n options: [\n { value: 'project', label: 'Project', hint: `${relative(process.cwd(), projectRoot) || '.'}/.claude/skills · 이 프로젝트만` },\n { value: 'global', label: 'Global', hint: '~/.claude/skills · 모든 프로젝트 (skills + sub-agents, hooks/settings 제외)' },\n ],\n initialValue: 'project',\n });\n if (p.isCancel(scopeResult)) { p.cancel('Install cancelled.'); return 130; }\n scope = scopeResult as 'project' | 'global';\n }\n\n // --- Skills — --skills pins it, else ask / default ALL.\n let skills: string[];\n if (opts.skillsFilter) {\n skills = allSkills.filter((s) => opts.skillsFilter!.includes(s));\n } else if (interactive) {\n const skillResult = await p.multiselect({\n message: '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)) { p.cancel('Install cancelled.'); return 130; }\n skills = skillResult as string[];\n } else {\n skills = allSkills;\n }\n\n // --- Sub-agents — dropped by --skills-only, else --agents pins, else ask / ALL.\n let canonicalAgents: string[];\n if (minimal) {\n canonicalAgents = [];\n } else if (opts.agentsFilter) {\n canonicalAgents = allAgents.filter((a) => opts.agentsFilter!.includes(a.replace(/\\.md$/, '')));\n } else if (interactive && allAgents.length > 0) {\n const agentResult = await p.multiselect({\n message: 'Sub-agents · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allAgents.map((a) => ({ value: a, label: a.replace(/\\.md$/, ''), hint: 'subagent' })),\n initialValues: allAgents,\n required: false,\n });\n if (p.isCancel(agentResult)) { p.cancel('Install cancelled.'); return 130; }\n canonicalAgents = agentResult as string[];\n } else {\n canonicalAgents = allAgents;\n }\n\n // --- Channels / targets — the \"where do I install\" choice.\n // --agent pins it. Otherwise, in a TTY we ASK — limited to the channels the\n // selected skills actually support (claude-design is claude-code only, so its\n // picker shows just Claude Code). Non-TTY / --all falls back to auto-resolution.\n const supportedTargets = ((): SkillTarget[] => {\n const set = new Set<SkillTarget>(skills.flatMap((s) => skillSupportedChannels(packageRoot, s)));\n return (['claude-code', 'codex', 'opencode'] as SkillTarget[]).filter((t) => set.has(t));\n })();\n const channelLabel: Record<SkillTarget, string> = {\n 'claude-code': 'Claude Code',\n codex: 'Codex',\n opencode: 'OpenCode',\n };\n const channelDir: Record<SkillTarget, string> = {\n 'claude-code': '.claude',\n codex: '.codex',\n opencode: '.opencode',\n };\n let targets: SkillTarget[];\n if (opts.agents) {\n targets = opts.agents;\n } else if (interactive) {\n const defaults = actuallyDetected.filter((t) => supportedTargets.includes(t));\n const targetResult = await p.multiselect({\n message: 'Agent channels · 어디에 설치할까요? · space = 토글 · enter = 확인',\n options: supportedTargets.map((t) => ({\n value: t,\n label: channelLabel[t],\n hint: actuallyDetected.includes(t) ? `${channelDir[t]}/ detected` : '',\n })) as { value: SkillTarget; label: string; hint?: string }[],\n initialValues: defaults.length > 0 ? defaults : supportedTargets,\n required: true,\n });\n if (p.isCancel(targetResult)) { p.cancel('Install cancelled.'); return 130; }\n targets = targetResult as SkillTarget[];\n } else {\n // Non-interactive (CI / piped / --all): resolve from flags + detection,\n // narrowed to channels the selected skills support.\n targets = opts.all\n ? (['claude-code', 'codex', 'opencode'] as SkillTarget[])\n : minimal\n ? (actuallyDetected.length > 0 ? actuallyDetected : (['claude-code'] as SkillTarget[]))\n : detected;\n const narrowed = targets.filter((t) => supportedTargets.includes(t));\n if (narrowed.length > 0) targets = narrowed;\n }\n\n // Global scope roots everything at the home dir, so plan dirs resolve to\n // ~/.claude/skills, ~/.claude/agents, etc. Project scope uses cwd (or --dir).\n const installRoot = scope === 'global' ? homedir() : projectRoot;\n const plans = targets.map((t) => planForTarget(installRoot, t));\n\n p.log.message(\n pc.bold('Scope: ') +\n pc.cyan(scope) +\n pc.dim(scope === 'global' ? ' (~/.claude)' : ` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n p.log.message(\n pc.bold(`Skills (${skills.length}): `) +\n skills.map((s) => pc.cyan(s)).join(', ')\n );\n if (minimal) {\n // --skills-only: sub-agents are intentionally skipped (minimal single-skill\n // install). Clear BEFORE the summary so we never print agents we won't write.\n canonicalAgents = [];\n p.log.message(pc.bold('Agents: ') + pc.dim('skipped (--skills-only)'));\n } else 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, installRoot, 'claude', filename, force));\n }\n } else if (target === 'codex') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, installRoot, 'codex', filename, force));\n }\n }\n // OpenCode currently has no agent-definition channel — skills only.\n }\n\n if (!minimal) {\n // Ship the read-only data assets (reference fingerprints, controlled vocab,\n // human-readable tag matrix, opt-out corpus) so skills + hooks can run entirely\n // 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, installRoot, '.claude', dataFile, force));\n }\n } else if (target === 'codex') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, installRoot, '.codex', dataFile, force));\n }\n }\n }\n\n // Hooks + settings.json are PROJECT-SCOPED only — a global install must not\n // mutate the user's global Claude config / make hooks fire in every project.\n if (scope === 'project' && 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, installRoot, hookFile, force));\n }\n // settings.json (with merge, never clobber user)\n results.push(installSettingsJson(packageRoot, installRoot, force));\n }\n } // !minimal — skills-only skips data files, hooks, and settings.json\n\n p.log.message(pc.bold('\\nResults:'));\n for (const r of results) {\n const rel = relative(installRoot, 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 // Minimal single-skill install (--skills-only): no omd onboarding, no agents/hooks.\n // Ideal for shipping a standalone skill (e.g. claude-design) to people who don't\n // want the rest of the omd toolchain.\n if (minimal) {\n for (const r of results.filter((x) => x.status === 'skipped-incompat')) {\n p.log.warn(\n `${pc.bold(r.skill)} ${pc.dim('skipped for ')}${pc.cyan(r.target)}${pc.dim(' — declares x-omd-channels (channel not supported).')}`\n );\n }\n const installed = results.filter(\n (r) => r.status === 'created' || r.status === 'updated'\n );\n if (installed.length === 0) {\n p.outro(pc.yellow('Nothing installed — no compatible skill/channel match.'));\n return 0;\n }\n p.outro(\n pc.green(\n `Done. Installed ${skills.map((s) => pc.bold(s)).join(', ')} ${scope === 'global' ? 'globally (~/.claude/skills)' : `for ${targets.join(', ')}`}.`\n ) +\n pc.dim(' → restart your agent, then use the skill (e.g. ') +\n pc.cyan('/claude-design') +\n pc.dim(').')\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 = scope === 'project' && 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)${scope === 'global' ? ' globally (~/.claude)' : ''}.`,\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"]}
|