windowook-skills 1.0.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/README.md +88 -0
- package/bin/cli.js +344 -0
- package/package.json +30 -0
- package/skills/clean-tailwind/SKILL.md +131 -0
- package/skills/code-flow-report/SKILL.md +112 -0
- package/skills/code-flow-report/assets/template.html +1458 -0
- package/skills/excalidraw/README.md +76 -0
- package/skills/excalidraw/SKILL.md +281 -0
- package/skills/excalidraw/references/arrows.md +288 -0
- package/skills/excalidraw/references/colors.md +91 -0
- package/skills/excalidraw/references/examples.md +381 -0
- package/skills/excalidraw/references/export.md +124 -0
- package/skills/excalidraw/references/json-format.md +210 -0
- package/skills/excalidraw/references/validation.md +182 -0
- package/skills/execute-figma-plan/SKILL.md +54 -0
- package/skills/figma-plan/CHECKLIST.md +32 -0
- package/skills/figma-plan/PRINCIPLES.md +41 -0
- package/skills/figma-plan/SKILL.md +44 -0
- package/skills/figma-plan/TEMPLATE.md +55 -0
- package/skills/figma-reference-plan/SKILL.md +69 -0
- package/skills/figma-reference-plan/TEMPLATE.md +83 -0
- package/skills/save-context/SKILL.md +79 -0
- package/skills/save-context/TEMPLATE.md +73 -0
- package/skills/save-context/references/example.md +74 -0
- package/skills/save-context/references/guide.md +106 -0
- package/skills/slack-thread/SKILL.md +80 -0
- package/skills/slack-thread/templates/MEETING.md +39 -0
- package/skills/slack-thread/templates/REPORT.md +64 -0
- package/skills/slack-thread/templates/SUGGESTION.md +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Claude-Code-Lab Skills Installer
|
|
2
|
+
|
|
3
|
+
Claude Code 스킬을 개별 또는 번들로 설치하는 CLI 도구입니다. (by window-ook)
|
|
4
|
+
|
|
5
|
+
<br>
|
|
6
|
+
|
|
7
|
+
## 📖 사용법
|
|
8
|
+
|
|
9
|
+
### 스킬 & 번들 목록 보기
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx windowook-skills list
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 개별 스킬 설치
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx windowook-skills install <skill-name>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 번들 설치
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx windowook-skills install --bundle <bundle-name>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 모든 스킬 설치
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx windowook-skills install --all
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
<br>
|
|
34
|
+
|
|
35
|
+
## 📦 번들
|
|
36
|
+
|
|
37
|
+
| 번들 | 설명 | 포함 스킬 |
|
|
38
|
+
| ------------ | ---------------------------- | --------------------------------------------------------------------------------------- |
|
|
39
|
+
| `analysis` | 코드 분석 & 시각화 스킬 그룹 | `code-flow-report`, `excalidraw` |
|
|
40
|
+
| `practical` | 범용 실무 스킬 그룹 | `save-context`, `slack-thread`, `clean-tailwind`, `execute-figma-plan`, `figma-plan`, `figma-reference-plan` |
|
|
41
|
+
|
|
42
|
+
<br>
|
|
43
|
+
|
|
44
|
+
## 🔧 개별 스킬
|
|
45
|
+
|
|
46
|
+
| 스킬 | 설명 |
|
|
47
|
+
| ---------------------- | -------------------------------------------- |
|
|
48
|
+
| `code-flow-report` | 코드 플로우 시각화 리포트 생성 |
|
|
49
|
+
| `excalidraw` | Excalidraw 아키텍처 다이어그램 생성 |
|
|
50
|
+
| `save-context` | 작업 컨텍스트 저장 및 복구 |
|
|
51
|
+
| `slack-thread` | Slack 스레드용 문서 정리 (회의록/건의/보고서) |
|
|
52
|
+
| `clean-tailwind` | Tailwind CSS 클래스 순서 정렬 |
|
|
53
|
+
| `execute-figma-plan` | Figma 구현 계획서 실행 |
|
|
54
|
+
| `figma-plan` | Figma 디자인 분석 → 구현 계획서 작성 |
|
|
55
|
+
| `figma-reference-plan` | Figma + 참조 코드 기반 구현 계획서 작성 |
|
|
56
|
+
|
|
57
|
+
<br>
|
|
58
|
+
|
|
59
|
+
## 📍 설치 위치
|
|
60
|
+
|
|
61
|
+
스킬은 현재 디렉토리의 `.claude/skills/` 폴더에 설치됩니다.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
your-project/
|
|
65
|
+
└── .claude/
|
|
66
|
+
└── skills/
|
|
67
|
+
└── excalidraw/ # 설치된 스킬
|
|
68
|
+
├── SKILL.md
|
|
69
|
+
└── references/
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
<br>
|
|
73
|
+
|
|
74
|
+
## 💡 예시
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# 개별 스킬 설치
|
|
78
|
+
npx windowook-skills install excalidraw
|
|
79
|
+
|
|
80
|
+
# 코드 분석 번들 설치
|
|
81
|
+
npx windowook-skills install --bundle analysis
|
|
82
|
+
|
|
83
|
+
# 실무 번들 설치
|
|
84
|
+
npx windowook-skills install --bundle practical
|
|
85
|
+
|
|
86
|
+
# 전체 설치
|
|
87
|
+
npx windowook-skills install --all
|
|
88
|
+
```
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const SKILLS_DIR = path.join(__dirname, '..', 'skills');
|
|
7
|
+
const TARGET_DIR = path.join(process.cwd(), '.claude', 'skills');
|
|
8
|
+
|
|
9
|
+
const C = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
bold: '\x1b[1m',
|
|
12
|
+
dim: '\x1b[2m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
magenta: '\x1b[35m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
white: '\x1b[37m',
|
|
20
|
+
orange: '\x1b[38;5;208m',
|
|
21
|
+
gold: '\x1b[38;5;220m',
|
|
22
|
+
peach: '\x1b[38;5;216m',
|
|
23
|
+
gray: '\x1b[38;5;245m',
|
|
24
|
+
lightBlue: '\x1b[38;5;117m',
|
|
25
|
+
pink: '\x1b[38;5;213m',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function printLogo() {
|
|
29
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
30
|
+
|
|
31
|
+
log('');
|
|
32
|
+
log(` ${C.orange} ╲ │ ╱${C.reset}`);
|
|
33
|
+
log(` ${C.orange} ╲ │ ╱${C.reset}`);
|
|
34
|
+
log(` ${C.orange} ──── ${C.gold}${C.bold}✦${C.reset}${C.orange} ────${C.reset}`);
|
|
35
|
+
log(` ${C.orange} ╱ │ ╲${C.reset}`);
|
|
36
|
+
log(` ${C.orange} ╱ │ ╲${C.reset}`);
|
|
37
|
+
log('');
|
|
38
|
+
log(` ${C.gold}${C.bold} Claude-Code-Lab${C.reset}`);
|
|
39
|
+
log(` ${C.peach} by window-ook${C.reset}`);
|
|
40
|
+
log(` ${C.gray} v${pkg.version}${C.reset}`);
|
|
41
|
+
log('');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const BUNDLES = {
|
|
45
|
+
analysis: {
|
|
46
|
+
name: 'Analysis',
|
|
47
|
+
emoji: '🔬',
|
|
48
|
+
description: '코드 분석 & 시각화 스킬 그룹',
|
|
49
|
+
skills: ['code-flow-report', 'excalidraw'],
|
|
50
|
+
},
|
|
51
|
+
practical: {
|
|
52
|
+
name: 'Practical',
|
|
53
|
+
emoji: '🛠️',
|
|
54
|
+
description: '범용 실무 스킬 그룹',
|
|
55
|
+
skills: [
|
|
56
|
+
'save-context',
|
|
57
|
+
'slack-thread',
|
|
58
|
+
'clean-tailwind',
|
|
59
|
+
'execute-figma-plan',
|
|
60
|
+
'figma-plan',
|
|
61
|
+
'figma-reference-plan',
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const SKILL_DESCRIPTIONS = {
|
|
67
|
+
'code-flow-report': '코드 플로우 시각화 리포트 생성',
|
|
68
|
+
'excalidraw': 'Excalidraw 아키텍처 다이어그램 생성',
|
|
69
|
+
'save-context': '작업 컨텍스트 저장 및 복구',
|
|
70
|
+
'slack-thread': 'Slack 스레드용 문서 정리 (회의록/건의/보고서)',
|
|
71
|
+
'clean-tailwind': 'Tailwind CSS 클래스 순서 정렬',
|
|
72
|
+
'execute-figma-plan': 'Figma 구현 계획서 실행',
|
|
73
|
+
'figma-plan': 'Figma 디자인 분석 → 구현 계획서 작성',
|
|
74
|
+
'figma-reference-plan': 'Figma + 참조 코드 기반 구현 계획서 작성',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function log(msg) {
|
|
78
|
+
console.log(msg);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getAvailableSkills() {
|
|
82
|
+
if (!fs.existsSync(SKILLS_DIR)) return [];
|
|
83
|
+
|
|
84
|
+
return fs.readdirSync(SKILLS_DIR).filter(name => {
|
|
85
|
+
const skillPath = path.join(SKILLS_DIR, name);
|
|
86
|
+
return fs.statSync(skillPath).isDirectory() &&
|
|
87
|
+
fs.existsSync(path.join(skillPath, 'SKILL.md'));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function collectFiles(dir, base = '') {
|
|
92
|
+
const results = [];
|
|
93
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
94
|
+
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
const rel = base ? `${base}/${entry.name}` : entry.name;
|
|
97
|
+
|
|
98
|
+
if (entry.isDirectory()) {
|
|
99
|
+
results.push({ type: 'dir', path: rel });
|
|
100
|
+
results.push(...collectFiles(path.join(dir, entry.name), rel));
|
|
101
|
+
} else {
|
|
102
|
+
results.push({ type: 'file', path: rel });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function copyDirRecursive(src, dest) {
|
|
110
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
111
|
+
|
|
112
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
113
|
+
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const srcPath = path.join(src, entry.name);
|
|
116
|
+
const destPath = path.join(dest, entry.name);
|
|
117
|
+
|
|
118
|
+
if (entry.isDirectory()) copyDirRecursive(srcPath, destPath);
|
|
119
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function installSkill(skillName, { verbose = false } = {}) {
|
|
124
|
+
const skillSrc = path.join(SKILLS_DIR, skillName);
|
|
125
|
+
const skillDest = path.join(TARGET_DIR, skillName);
|
|
126
|
+
|
|
127
|
+
if (!fs.existsSync(skillSrc)) {
|
|
128
|
+
log(` ${C.red}✗ 오류: '${skillName}' 스킬을 찾을 수 없습니다.${C.reset}`);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (fs.existsSync(skillDest)) {
|
|
133
|
+
log(` ${C.yellow}⚠ ${skillName} 이미 존재합니다. 덮어씁니다.${C.reset}`);
|
|
134
|
+
fs.rmSync(skillDest, { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
log(` ${C.orange}🚀 Installing skill: ${C.bold}${skillName}${C.reset}`);
|
|
138
|
+
|
|
139
|
+
// 파일별 상세 로그
|
|
140
|
+
const files = collectFiles(skillSrc);
|
|
141
|
+
|
|
142
|
+
for (const f of files) {
|
|
143
|
+
if (f.type === 'dir') log(` ${C.gray}📂 Copying directory: ${C.lightBlue}${f.path}/${C.reset}`);
|
|
144
|
+
else log(` ${C.green}✓${C.reset} ${C.bold}Copied: ${C.lightBlue}${f.path}${C.reset}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fs.mkdirSync(TARGET_DIR, { recursive: true });
|
|
148
|
+
copyDirRecursive(skillSrc, skillDest);
|
|
149
|
+
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function installBundle(bundleName) {
|
|
154
|
+
const bundle = BUNDLES[bundleName];
|
|
155
|
+
|
|
156
|
+
if (!bundle) {
|
|
157
|
+
log(`\n ${C.red}✗ 오류: '${bundleName}' 번들을 찾을 수 없습니다.${C.reset}`);
|
|
158
|
+
log(` ${C.yellow}사용 가능한 번들:${C.reset}`);
|
|
159
|
+
Object.entries(BUNDLES).forEach(([key, b]) => {
|
|
160
|
+
log(` ${C.cyan}${b.emoji} ${key}${C.reset} — ${b.description}`);
|
|
161
|
+
});
|
|
162
|
+
log('');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
log('');
|
|
167
|
+
log(` ${C.gold}${bundle.emoji} Installing bundle: ${C.bold}${bundle.name}${C.reset}`);
|
|
168
|
+
log(` ${C.gold}${bundle.description}${C.reset}`);
|
|
169
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
170
|
+
log(` ${C.pink}📦 Installing ${bundle.skills.length} skills...${C.reset}`);
|
|
171
|
+
log('');
|
|
172
|
+
|
|
173
|
+
let successCount = 0;
|
|
174
|
+
for (const skill of bundle.skills) {
|
|
175
|
+
if (installSkill(skill)) successCount++;
|
|
176
|
+
log('');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
180
|
+
log(` ${C.green}✅ 완료: ${C.bold}${successCount}/${bundle.skills.length}${C.reset}${C.green}개 스킬 설치됨${C.reset}`);
|
|
181
|
+
log(` ${C.cyan}📍 경로: ${TARGET_DIR}${C.reset}`);
|
|
182
|
+
log('');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function listSkills() {
|
|
186
|
+
const skills = getAvailableSkills();
|
|
187
|
+
|
|
188
|
+
printLogo();
|
|
189
|
+
|
|
190
|
+
if (skills.length === 0) {
|
|
191
|
+
log(` ${C.yellow}설치 가능한 스킬이 없습니다.${C.reset}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 번들 목록
|
|
196
|
+
log(` ${C.orange}📦 Bundles${C.reset}`);
|
|
197
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
198
|
+
|
|
199
|
+
Object.entries(BUNDLES).forEach(([key, bundle]) => {
|
|
200
|
+
log(` ${bundle.emoji} ${C.bold}${C.gold}${key}${C.reset} ${C.gray}— ${bundle.description} (${bundle.skills.length}개)${C.reset}`);
|
|
201
|
+
bundle.skills.forEach(s => {
|
|
202
|
+
const desc = SKILL_DESCRIPTIONS[s] || '';
|
|
203
|
+
log(` ${C.gray}└─${C.reset} ${C.lightBlue}${s}${C.reset} ${C.dim}${desc}${C.reset}`);
|
|
204
|
+
});
|
|
205
|
+
log('');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// 개별 스킬 목록
|
|
209
|
+
log(` ${C.orange}🔧 Skills${C.reset}`);
|
|
210
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
211
|
+
|
|
212
|
+
skills.forEach(skill => {
|
|
213
|
+
const desc = SKILL_DESCRIPTIONS[skill] || '설명 없음';
|
|
214
|
+
log(` ${C.green}●${C.reset} ${C.bold}${skill}${C.reset}`);
|
|
215
|
+
log(` ${C.dim}${desc}${C.reset}`);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
log('');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function showHelp() {
|
|
222
|
+
printLogo();
|
|
223
|
+
log(` ${C.orange}📖 Usage${C.reset}`);
|
|
224
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
225
|
+
log(` ${C.white}npx windowook-skills ${C.green}list${C.reset} ${C.dim}스킬 & 번들 목록${C.reset}`);
|
|
226
|
+
log(` ${C.white}npx windowook-skills ${C.green}install ${C.lightBlue}<name>${C.reset} ${C.dim}개별 스킬 설치${C.reset}`);
|
|
227
|
+
log(` ${C.white}npx windowook-skills ${C.green}install ${C.gold}--bundle ${C.lightBlue}<name>${C.reset} ${C.dim}번들 설치${C.reset}`);
|
|
228
|
+
log(` ${C.white}npx windowook-skills ${C.green}install ${C.gold}--all${C.reset} ${C.dim}모든 스킬 설치${C.reset}`);
|
|
229
|
+
log('');
|
|
230
|
+
log(` ${C.orange}📦 Bundles${C.reset}`);
|
|
231
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
232
|
+
|
|
233
|
+
Object.entries(BUNDLES).forEach(([key, bundle]) => {
|
|
234
|
+
log(` ${bundle.emoji} ${C.bold}${C.gold}${key}${C.reset} ${C.dim}${bundle.description} (${bundle.skills.length}개)${C.reset}`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
log('');
|
|
238
|
+
log(` ${C.orange}💡 Examples${C.reset}`);
|
|
239
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
240
|
+
log(` ${C.cyan}$ npx windowook-skills install excalidraw${C.reset}`);
|
|
241
|
+
log(` ${C.cyan}$ npx windowook-skills install --bundle analysis${C.reset}`);
|
|
242
|
+
log(` ${C.cyan}$ npx windowook-skills install --all${C.reset}`);
|
|
243
|
+
log('');
|
|
244
|
+
log(` ${C.orange}📍 Install Path${C.reset}`);
|
|
245
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
246
|
+
log(` ${C.dim}현재 디렉토리의 ${C.lightBlue}.claude/skills/${C.reset}${C.dim} 폴더에 설치됩니다.${C.reset}`);
|
|
247
|
+
log('');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function main() {
|
|
251
|
+
const args = process.argv.slice(2);
|
|
252
|
+
const command = args[0];
|
|
253
|
+
|
|
254
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
255
|
+
showHelp();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (command === 'list' || command === 'ls') {
|
|
260
|
+
listSkills();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (command === 'install' || command === 'i') {
|
|
265
|
+
const option = args[1];
|
|
266
|
+
|
|
267
|
+
if (!option) {
|
|
268
|
+
log(`\n ${C.red}✗ 오류: 설치할 스킬 또는 옵션을 입력해주세요.${C.reset}`);
|
|
269
|
+
log(` ${C.dim}사용법: npx windowook-skills install <skill-name>${C.reset}`);
|
|
270
|
+
log(` ${C.dim} npx windowook-skills install --bundle <bundle-name>${C.reset}`);
|
|
271
|
+
log(` ${C.dim} npx windowook-skills install --all${C.reset}`);
|
|
272
|
+
log(` ${C.dim}목록 보기: npx windowook-skills list${C.reset}\n`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// --all: 모든 스킬
|
|
277
|
+
if (option === '--all' || option === '-a') {
|
|
278
|
+
const skills = getAvailableSkills();
|
|
279
|
+
|
|
280
|
+
log('');
|
|
281
|
+
log(` ${C.gold}🚀 Installing all skills...${C.reset}`);
|
|
282
|
+
log(` ${C.pink}📦 Installing ${skills.length} skills...${C.reset}`);
|
|
283
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
284
|
+
log('');
|
|
285
|
+
|
|
286
|
+
let successCount = 0;
|
|
287
|
+
for (const skill of skills) {
|
|
288
|
+
if (installSkill(skill)) successCount++;
|
|
289
|
+
log('');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
293
|
+
log(` ${C.green}✅ 완료: ${C.bold}${successCount}/${skills.length}${C.reset}${C.green}개 스킬 설치됨${C.reset}`);
|
|
294
|
+
log(` ${C.cyan}📍 경로: ${TARGET_DIR}${C.reset}`);
|
|
295
|
+
log('');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// --bundle: 번들 설치
|
|
300
|
+
if (option === '--bundle' || option === '-b') {
|
|
301
|
+
const bundleName = args[2];
|
|
302
|
+
|
|
303
|
+
if (!bundleName) {
|
|
304
|
+
log(`\n ${C.red}✗ 오류: 번들 이름을 입력해주세요.${C.reset}`);
|
|
305
|
+
log(` ${C.yellow}사용 가능한 번들:${C.reset}`);
|
|
306
|
+
Object.entries(BUNDLES).forEach(([key, bundle]) => {
|
|
307
|
+
log(` ${C.cyan}${bundle.emoji} ${key}${C.reset} — ${bundle.description}`);
|
|
308
|
+
});
|
|
309
|
+
log('');
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
installBundle(bundleName);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 개별 스킬 설치
|
|
318
|
+
const skillName = option;
|
|
319
|
+
const available = getAvailableSkills();
|
|
320
|
+
|
|
321
|
+
if (!available.includes(skillName)) {
|
|
322
|
+
log(`\n ${C.red}✗ 오류: '${skillName}' 스킬을 찾을 수 없습니다.${C.reset}`);
|
|
323
|
+
log(` ${C.yellow}사용 가능한 스킬:${C.reset}`);
|
|
324
|
+
available.forEach(s => log(` ${C.green}●${C.reset} ${s}`));
|
|
325
|
+
log(`\n ${C.dim}번들 설치: npx windowook-skills install --bundle <name>${C.reset}\n`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
log('');
|
|
330
|
+
installSkill(skillName);
|
|
331
|
+
log('');
|
|
332
|
+
log(` ${C.gray}${'─'.repeat(45)}${C.reset}`);
|
|
333
|
+
log(` ${C.green}✅ 설치 완료${C.reset}`);
|
|
334
|
+
log(` ${C.cyan}📍 경로: ${TARGET_DIR}/${skillName}${C.reset}`);
|
|
335
|
+
log('');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
log(`\n ${C.red}✗ 알 수 없는 명령어: ${command}${C.reset}`);
|
|
340
|
+
showHelp();
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "windowook-skills",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Claude Code 스킬을 개별 또는 번들로 설치하는 CLI 도구 (by window-ook)",
|
|
5
|
+
"bin": {
|
|
6
|
+
"windowook-skills": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"skills",
|
|
12
|
+
"ai",
|
|
13
|
+
"cli",
|
|
14
|
+
"window-ook"
|
|
15
|
+
],
|
|
16
|
+
"author": "window-ook",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/window-ook/claude-code-lab"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin",
|
|
24
|
+
"skills",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clean-tailwind
|
|
3
|
+
description: Use when refactoring Tailwind CSS class order in React/Next.js components, when className strings feel disorganized, or when establishing consistent CSS ordering conventions across a codebase
|
|
4
|
+
argument-hint: '[파일 또는 디렉토리 경로]'
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Clean Tailwind
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Tailwind CSS 클래스를 일관된 순서로 정렬하는 기법입니다. **핵심 원칙**: 시각적 렌더링 순서(바깥→안쪽→콘텐츠)를 따릅니다.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
**Use when:**
|
|
17
|
+
|
|
18
|
+
- `className` 문자열이 길고 무질서해 보일 때
|
|
19
|
+
- 팀 코드 리뷰에서 Tailwind 순서 일관성 문제가 제기될 때
|
|
20
|
+
- 새 프로젝트에 Tailwind 정렬 규칙을 도입할 때
|
|
21
|
+
|
|
22
|
+
**Don't use for:**
|
|
23
|
+
|
|
24
|
+
- shadcn/ui, Radix 등 외부 라이브러리 컴포넌트
|
|
25
|
+
- CSS-in-JS나 styled-components 프로젝트
|
|
26
|
+
|
|
27
|
+
## Quick Reference
|
|
28
|
+
|
|
29
|
+
| 순위 | 카테고리 | 속성 |
|
|
30
|
+
| ---- | -------------- | ------------------------------------------------------------------------------------------- |
|
|
31
|
+
| 0 | 커스텀 CSS | `card-tilt`, `hover-button` 등 프로젝트 커스텀 클래스 (맨 앞) |
|
|
32
|
+
| 1 | 포지션 | `absolute`, `relative`, `fixed`, `sticky`, `static`, `inset-*`, `top-*`, `right-*`, `bottom-*`, `left-*`, `z-*` |
|
|
33
|
+
| 2 | 레이아웃 | `w-*`, `h-*`, `size-*`, `min-w-*`, `max-w-*`, `min-h-*`, `max-h-*`, `overflow-*`, `aspect-*`, `container`, `block`, `inline-*`, `hidden`, `visible`, `invisible` |
|
|
34
|
+
| 3 | 공백 | `m-*`, `mx-*`, `my-*`, `mt-*`, `mr-*`, `mb-*`, `ml-*`, `p-*`, `px-*`, `py-*`, `pt-*`, `pr-*`, `pb-*`, `pl-*`, `gap-*`, `gap-x-*`, `gap-y-*`, `space-x-*`, `space-y-*` |
|
|
35
|
+
| 4 | 외곽 효과 | `border-*`, `rounded-*`, `shadow-*`, `ring-*`, `outline-*`, `divide-*` |
|
|
36
|
+
| 5 | 배경색 | `bg-*`, `opacity-*`, `backdrop-*`, `gradient-*`, `from-*`, `via-*`, `to-*` |
|
|
37
|
+
| 6 | Flex/Grid | `flex`, `flex-row`, `flex-col`, `flex-wrap`, `flex-1`, `grow`, `shrink`, `basis-*`, `grid`, `grid-cols-*`, `grid-rows-*`, `col-span-*`, `row-span-*`, `justify-*`, `items-*`, `self-*`, `place-*`, `order-*` |
|
|
38
|
+
| 7 | 폰트 | `text-*`, `font-*`, `leading-*`, `tracking-*`, `whitespace-*`, `truncate`, `line-clamp-*`, `break-*`, `underline`, `line-through`, `no-underline`, `uppercase`, `lowercase`, `capitalize`, `italic`, `not-italic`, `list-*`, `indent-*` |
|
|
39
|
+
| 8 | 애니메이션 | `animate-*` |
|
|
40
|
+
| 9 | 트랜지션 | `transition-*`, `duration-*`, `ease-*`, `delay-*` |
|
|
41
|
+
| - | 인터랙션 | `cursor-*`, `pointer-events-*`, `select-*`, `touch-*`, `scroll-*`, `snap-*`, `will-change-*` — 해당 속성 바로 뒤에 배치 (hover:/focus:와 동일 위치) |
|
|
42
|
+
| - | 조건부 | `hover:`, `focus:`, `active:`, `disabled:`, `group-hover:`, `peer-*:` — 해당 기본 클래스 바로 뒤에 배치 |
|
|
43
|
+
| - | 반응형 | `sm:`, `md:`, `lg:`, `xl:`, `2xl:` — 해당 기본 클래스 바로 뒤에 배치 |
|
|
44
|
+
|
|
45
|
+
## Core Pattern
|
|
46
|
+
|
|
47
|
+
### Before/After
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// ❌ Before: 무질서한 순서
|
|
51
|
+
className =
|
|
52
|
+
'px-4 hover:bg-blue-600 py-2 bg-blue-500 text-white rounded-lg font-bold';
|
|
53
|
+
|
|
54
|
+
// ✅ After: 정렬된 순서 (외곽→배경→공백→폰트)
|
|
55
|
+
className =
|
|
56
|
+
'rounded-lg bg-blue-500 hover:bg-blue-600 px-4 py-2 font-bold text-white';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 조건부 클래스 배치
|
|
60
|
+
|
|
61
|
+
조건부 클래스(`hover:`, `focus:`, `disabled:`)는 **해당 기본 클래스 바로 뒤**에 배치:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// ✅ 올바름: bg-blue-500 바로 뒤에 hover:bg-blue-600
|
|
65
|
+
className = 'rounded-lg bg-blue-500 hover:bg-blue-600 px-4 py-2';
|
|
66
|
+
|
|
67
|
+
// ❌ 잘못됨: hover가 bg와 분리됨
|
|
68
|
+
className = 'rounded-lg bg-blue-500 px-4 py-2 hover:bg-blue-600';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 동일 크기는 size-\* 사용
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// ❌ Before
|
|
75
|
+
className = 'w-8 h-8';
|
|
76
|
+
|
|
77
|
+
// ✅ After
|
|
78
|
+
className = 'size-8';
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Dynamic Classes
|
|
82
|
+
|
|
83
|
+
### 템플릿 리터럴 처리
|
|
84
|
+
|
|
85
|
+
동적 클래스가 포함된 경우, 정적 부분만 정렬하고 동적 부분은 원래 위치 유지:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// 동적 클래스 (${cardColor})는 해당 카테고리 위치에 배치
|
|
89
|
+
className={`card-tilt size-full p-4 border-6 ${cardColorByGrade} rounded-2xl flex flex-col`}
|
|
90
|
+
// 커스텀 레이아웃 공백 외곽 동적(외곽/배경) 외곽 Flex
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 조건부 스타일 객체
|
|
94
|
+
|
|
95
|
+
조건부 스타일 객체는 순서 변경 없이 유지:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
className={cn(
|
|
99
|
+
"rounded-lg bg-white px-4 py-2", // 정적 부분만 정렬
|
|
100
|
+
isDarkMode && "bg-gray-800", // 조건부는 그대로
|
|
101
|
+
isActive && "ring-2 ring-blue-500" // 조건부는 그대로
|
|
102
|
+
)}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Common Mistakes
|
|
106
|
+
|
|
107
|
+
| 실수 | 수정 |
|
|
108
|
+
| ----------------------------- | ------------------------------------- |
|
|
109
|
+
| 공백이 외곽보다 먼저 | `px-4 rounded-lg` → `rounded-lg px-4` |
|
|
110
|
+
| 포지션이 레이아웃 뒤에 | `w-full relative` → `relative w-full` |
|
|
111
|
+
| 조건부가 기본 클래스와 분리됨 | 기본 클래스 바로 뒤로 이동 |
|
|
112
|
+
| w-8 h-8 중복 | `size-8`로 통합 |
|
|
113
|
+
|
|
114
|
+
## Batch Workflow
|
|
115
|
+
|
|
116
|
+
대규모 리팩토링 시 권장 워크플로우:
|
|
117
|
+
|
|
118
|
+
1. **브랜치 생성**: `git checkout -b refactor/tailwind-class-order`
|
|
119
|
+
2. **배치 분할**: 5-8개 파일/배치로 나누기
|
|
120
|
+
3. **배치 작업**: 파일 읽기 → 정렬 → 저장 → UI 확인
|
|
121
|
+
4. **배치 커밋**: 각 배치별 커밋으로 롤백 용이성 확보
|
|
122
|
+
5. **최종 검증**: E2E 테스트 + 빌드 확인
|
|
123
|
+
|
|
124
|
+
## Red Flags
|
|
125
|
+
|
|
126
|
+
작업 시 주의 신호:
|
|
127
|
+
|
|
128
|
+
- 외부 라이브러리(shadcn-ui 등) 컴포넌트 수정하려 함
|
|
129
|
+
- 동적 클래스 로직을 변경하려 함
|
|
130
|
+
- 테스트 없이 여러 배치 연속 작업
|
|
131
|
+
- 커밋 없이 대량 파일 수정
|