@m1kapp/kit 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/favicon.mjs +0 -0
- package/bin/m1kkit.mjs +49 -0
- package/bin/postinstall.mjs +28 -0
- package/bin/skills/m1kapp-init.md +159 -0
- package/bin/skills/m1kapp-pwa.md +62 -0
- package/bin/skills/m1kapp-seo.md +66 -0
- package/bin/skills.mjs +97 -0
- package/dist/index.d.mts +51 -23
- package/dist/index.d.ts +51 -23
- package/dist/index.js +7 -8
- package/dist/index.mjs +7 -8
- package/dist/pwa.js +2 -2
- package/dist/pwa.mjs +1 -1
- package/dist/seo.d.mts +260 -0
- package/dist/seo.d.ts +260 -0
- package/dist/seo.js +4 -0
- package/dist/seo.mjs +4 -0
- package/dist/styles.css +1 -1
- package/package.json +8 -2
package/bin/favicon.mjs
CHANGED
|
File without changes
|
package/bin/m1kkit.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* m1kkit CLI
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* m1kkit favicon — 파비콘 생성
|
|
7
|
+
* m1kkit skills — Claude Code 스킬 설치
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const [,, command, ...rest] = process.argv;
|
|
11
|
+
|
|
12
|
+
if (!command || command === "--help" || command === "-h") {
|
|
13
|
+
console.log(`
|
|
14
|
+
m1kkit — @m1kapp/kit CLI
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
m1kkit favicon [options] 파비콘 자동 생성
|
|
18
|
+
m1kkit skills [options] Claude Code 스킬 설치
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--help 도움말 보기
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
m1kkit favicon --text=K --color=#3B82F6
|
|
25
|
+
m1kkit skills
|
|
26
|
+
m1kkit skills --list
|
|
27
|
+
m1kkit skills m1kapp-init
|
|
28
|
+
`);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 서브커맨드 위임
|
|
33
|
+
const { createRequire } = await import("module");
|
|
34
|
+
const { fileURLToPath } = await import("url");
|
|
35
|
+
const path = await import("path");
|
|
36
|
+
|
|
37
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
|
|
39
|
+
if (command === "favicon") {
|
|
40
|
+
process.argv = [process.argv[0], process.argv[1], ...rest];
|
|
41
|
+
await import(path.join(__dirname, "favicon.mjs"));
|
|
42
|
+
} else if (command === "skills") {
|
|
43
|
+
process.argv = [process.argv[0], process.argv[1], ...rest];
|
|
44
|
+
await import(path.join(__dirname, "skills.mjs"));
|
|
45
|
+
} else {
|
|
46
|
+
console.error(`알 수 없는 커맨드: ${command}`);
|
|
47
|
+
console.error("사용법: m1kkit --help");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// postinstall — @m1kapp/kit 설치 후 안내 메시지
|
|
3
|
+
// CI 환경이나 suppress 설정이면 조용히 종료
|
|
4
|
+
|
|
5
|
+
if (process.env.CI || process.env.npm_config_loglevel === "silent") {
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const v = process.env.npm_package_version ?? "";
|
|
10
|
+
const version = v ? ` v${v}` : "";
|
|
11
|
+
|
|
12
|
+
console.log(`
|
|
13
|
+
╭─────────────────────────────────────────────╮
|
|
14
|
+
│ │
|
|
15
|
+
│ @m1kapp/kit${version} 설치 완료! │
|
|
16
|
+
│ │
|
|
17
|
+
│ Claude Code 스킬 추가 (선택): │
|
|
18
|
+
│ npx m1kkit skills │
|
|
19
|
+
│ │
|
|
20
|
+
│ /m1kapp-init 프로젝트 초기 설정 │
|
|
21
|
+
│ /m1kapp-seo SEO 자동 감사·적용 │
|
|
22
|
+
│ /m1kapp-pwa PWA 설정 점검·적용 │
|
|
23
|
+
│ │
|
|
24
|
+
│ 업데이트 후엔 스킬도 재설치 권장: │
|
|
25
|
+
│ npx m1kkit skills │
|
|
26
|
+
│ │
|
|
27
|
+
╰─────────────────────────────────────────────╯
|
|
28
|
+
`);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: m1kapp-init
|
|
3
|
+
description: "@m1kapp/kit 기반 Next.js 프로젝트 초기 설정을 인터랙티브하게 완성합니다."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
현재 디렉토리가 Next.js + @m1kapp/kit 프로젝트인지 확인한 뒤, 아래 순서대로 진행한다.
|
|
7
|
+
|
|
8
|
+
## Step 0: 프로젝트 파악
|
|
9
|
+
|
|
10
|
+
먼저 조용히 다음을 확인한다 (사용자에게 보고하지 않음):
|
|
11
|
+
- `package.json` — 프레임워크, @m1kapp/kit 버전
|
|
12
|
+
- `app/layout.tsx` — 기존 metadata 여부
|
|
13
|
+
- `app/sitemap.ts`, `app/robots.ts` 존재 여부
|
|
14
|
+
- `public/` — 파비콘, OG 이미지 여부
|
|
15
|
+
- `tailwind.config.*` — 테마 컬러 여부
|
|
16
|
+
|
|
17
|
+
파악이 끝나면 아래 질문들을 **한 번에 모아서** 사용자에게 묻는다.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Step 1: 인터랙티브 질문
|
|
22
|
+
|
|
23
|
+
다음 항목들을 자연스럽게 한 번에 물어본다. 이미 파악한 정보는 기본값으로 채워서 제안한다.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
@m1kapp/kit 초기 설정을 시작할게요. 몇 가지만 확인할게요!
|
|
27
|
+
|
|
28
|
+
1. 앱 이름이 뭔가요?
|
|
29
|
+
(예: "My App", "스타트업 서비스명")
|
|
30
|
+
|
|
31
|
+
2. 메인 테마 컬러가 있나요?
|
|
32
|
+
(hex 코드로 알려주세요. 없으면 기본 파란색 #3B82F6 쓸게요)
|
|
33
|
+
|
|
34
|
+
3. 앱 한 줄 설명이 뭔가요?
|
|
35
|
+
(메타 description + OG에 사용됩니다)
|
|
36
|
+
|
|
37
|
+
4. 배포 URL이 있나요?
|
|
38
|
+
(예: https://myapp.com — SEO canonical, sitemap 등에 사용)
|
|
39
|
+
|
|
40
|
+
5. 앱 유형은 뭔가요?
|
|
41
|
+
(1) 커머스/쇼핑
|
|
42
|
+
(2) 블로그/콘텐츠
|
|
43
|
+
(3) 대시보드/툴
|
|
44
|
+
(4) 소셜/커뮤니티
|
|
45
|
+
(5) 랜딩/홍보
|
|
46
|
+
(6) 기타
|
|
47
|
+
|
|
48
|
+
6. 다음 중 적용할 것을 골라주세요 (복수 선택, 예: 1 2 3)
|
|
49
|
+
(1) SEO — metadata, sitemap, robots, JSON-LD
|
|
50
|
+
(2) PWA — manifest, 설치 유도 버튼, 아이콘
|
|
51
|
+
(3) OG 이미지 — /og 라우트 자동 생성
|
|
52
|
+
(4) 파비콘 — 자동 생성 (m1kkit 필요)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Step 2: 적용 계획 출력
|
|
58
|
+
|
|
59
|
+
답변을 받으면 아래 형식으로 적용 계획을 보여준다:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
✓ 확인했어요! 이렇게 적용할게요:
|
|
63
|
+
|
|
64
|
+
앱 이름: [앱 이름]
|
|
65
|
+
테마 컬러: [색상] ████
|
|
66
|
+
설명: [한 줄 설명]
|
|
67
|
+
배포 URL: [URL]
|
|
68
|
+
|
|
69
|
+
적용 항목:
|
|
70
|
+
☐ app/layout.tsx — metadata, titleTemplate 설정
|
|
71
|
+
☐ app/sitemap.ts — nextSitemap 생성
|
|
72
|
+
☐ app/robots.ts — nextRobots 생성
|
|
73
|
+
☐ app/og/route.tsx — OG 이미지 라우트
|
|
74
|
+
☐ app/manifest.ts — PWA manifest
|
|
75
|
+
☐ tailwind.config — 테마 컬러 등록
|
|
76
|
+
|
|
77
|
+
시작할까요? (y/n)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
y면 Step 3 진행, n이면 수정할 항목 다시 묻기.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Step 3: 파일 생성/수정
|
|
85
|
+
|
|
86
|
+
확인된 답변을 바탕으로 선택한 항목들을 순서대로 적용한다.
|
|
87
|
+
각 파일 완료 시 체크 표시:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
✓ app/layout.tsx 완료
|
|
91
|
+
✓ app/sitemap.ts 완료
|
|
92
|
+
✓ app/robots.ts 완료
|
|
93
|
+
...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### layout.tsx 적용 규칙
|
|
97
|
+
- 기존 파일이 있으면 `metadata` export만 교체, 나머지는 보존
|
|
98
|
+
- `@m1kapp/kit/seo`의 `createMetadata`, `titleTemplate` 사용
|
|
99
|
+
- 앱 유형에 따라 적절한 JSON-LD도 추가 (WebSite, Organization 등)
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { createMetadata, titleTemplate } from "@m1kapp/kit/seo"
|
|
103
|
+
|
|
104
|
+
export const metadata = createMetadata({
|
|
105
|
+
title: "[앱 이름]",
|
|
106
|
+
description: "[한 줄 설명]",
|
|
107
|
+
url: "[배포 URL]",
|
|
108
|
+
siteName: "[앱 이름]",
|
|
109
|
+
image: "[배포 URL]/og",
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### sitemap.ts 적용 규칙
|
|
114
|
+
- 이미 있으면 건너뛰고 사용자에게 알림
|
|
115
|
+
- `app/` 하위 `page.tsx` 파일 목록을 스캔해서 자동으로 경로 추출
|
|
116
|
+
- 동적 라우트(`[id]`)는 TODO 주석으로 표시
|
|
117
|
+
|
|
118
|
+
### robots.ts 적용 규칙
|
|
119
|
+
- `nextRobots` 사용
|
|
120
|
+
- `/api`, `/admin` 등 일반적인 disallow 패턴 자동 적용
|
|
121
|
+
|
|
122
|
+
### OG 이미지 적용 규칙
|
|
123
|
+
- `app/og/route.tsx` 생성
|
|
124
|
+
- `@m1kapp/kit/ogimage`의 템플릿 사용
|
|
125
|
+
- 앱 이름 + 테마 컬러 반영
|
|
126
|
+
|
|
127
|
+
### PWA manifest 적용 규칙
|
|
128
|
+
- `app/manifest.ts` 생성
|
|
129
|
+
- `@m1kapp/kit/pwa`의 `createManifest` 사용
|
|
130
|
+
- 테마 컬러 자동 반영
|
|
131
|
+
|
|
132
|
+
### tailwind 테마 컬러 적용 규칙
|
|
133
|
+
- `tailwind.config.ts` 또는 `globals.css`에 CSS 변수로 추가
|
|
134
|
+
- `--color-primary: [hex]` 형태로
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Step 4: 완료 요약
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
🎉 설정 완료!
|
|
142
|
+
|
|
143
|
+
적용된 파일:
|
|
144
|
+
✓ app/layout.tsx
|
|
145
|
+
✓ app/sitemap.ts
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
다음 단계:
|
|
149
|
+
→ 배포 후 https://search.google.com/search-console 에 사이트 등록
|
|
150
|
+
→ /og 라우트 접속해서 OG 이미지 확인
|
|
151
|
+
→ npx m1kkit favicon 으로 파비콘 생성
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 주의사항
|
|
157
|
+
- 기존 코드를 덮어쓸 때는 반드시 원본 내용을 보존하면서 필요한 부분만 추가/수정
|
|
158
|
+
- TypeScript 타입 에러가 없도록 lint 후 보고
|
|
159
|
+
- 파일 생성 전 이미 있는 경우 사용자에게 알리고 덮어쓸지 확인
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: m1kapp-pwa
|
|
3
|
+
description: "PWA 설정을 점검하고 @m1kapp/kit/pwa 유틸로 자동 적용합니다."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
현재 Next.js 프로젝트의 PWA 설정을 점검하고 빠진 항목을 채운다.
|
|
7
|
+
|
|
8
|
+
## Step 1: PWA 현황 파악
|
|
9
|
+
|
|
10
|
+
다음을 확인한다:
|
|
11
|
+
- `app/manifest.ts` 또는 `public/manifest.json` 존재 여부
|
|
12
|
+
- `app/viewport.ts` 또는 layout의 viewport 설정
|
|
13
|
+
- 아이콘: `public/icon-192.png`, `public/icon-512.png` 여부
|
|
14
|
+
- `public/favicon.ico` 여부
|
|
15
|
+
- 설치 유도 버튼 (`useInstallPrompt`) 사용 여부
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Step 2: 결과 리포트
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
PWA 설정 현황
|
|
23
|
+
|
|
24
|
+
✓ 완료
|
|
25
|
+
✓ ...
|
|
26
|
+
|
|
27
|
+
✗ 미설정
|
|
28
|
+
✗ app/manifest.ts
|
|
29
|
+
✗ 아이콘 (192×192, 512×512)
|
|
30
|
+
...
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Step 3: 보완
|
|
36
|
+
|
|
37
|
+
빠진 항목에 대해 확인 후 적용한다.
|
|
38
|
+
|
|
39
|
+
아이콘이 없으면: `npx m1kkit favicon` 명령어 안내
|
|
40
|
+
manifest가 없으면: 앱 이름, 테마 컬러를 물어보고 `createManifest` 적용
|
|
41
|
+
viewport가 없으면: `mobileViewport` 적용
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// app/manifest.ts
|
|
45
|
+
import { createManifest } from "@m1kapp/kit/pwa"
|
|
46
|
+
|
|
47
|
+
export default function manifest() {
|
|
48
|
+
return createManifest({
|
|
49
|
+
name: "[앱 이름]",
|
|
50
|
+
short_name: "[앱 이름]",
|
|
51
|
+
theme_color: "[테마 컬러]",
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
완료 후:
|
|
57
|
+
```
|
|
58
|
+
✓ PWA 설정 완료
|
|
59
|
+
|
|
60
|
+
→ 크롬에서 주소창 우측 설치 버튼 확인
|
|
61
|
+
→ Lighthouse PWA 탭에서 점수 확인
|
|
62
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: m1kapp-seo
|
|
3
|
+
description: "프로젝트 SEO 현황을 감사하고 @m1kapp/kit/seo 유틸로 자동 보완합니다."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
현재 Next.js 프로젝트의 SEO 상태를 점검하고, 빠진 항목을 @m1kapp/kit/seo로 채운다.
|
|
7
|
+
|
|
8
|
+
## Step 1: SEO 감사
|
|
9
|
+
|
|
10
|
+
다음 항목들을 병렬로 빠르게 확인한다:
|
|
11
|
+
|
|
12
|
+
**메타데이터**
|
|
13
|
+
- `app/layout.tsx` — metadata export 여부, title/description/og 필드 완성도
|
|
14
|
+
- 각 `page.tsx` — generateMetadata 또는 metadata export 여부
|
|
15
|
+
- title이 사이트명만 있고 페이지별 구분이 없는지
|
|
16
|
+
|
|
17
|
+
**크롤링/인덱싱**
|
|
18
|
+
- `app/sitemap.ts` 또는 `public/sitemap.xml` 존재 여부
|
|
19
|
+
- `app/robots.ts` 또는 `public/robots.txt` 존재 여부
|
|
20
|
+
- noIndex가 실수로 켜져 있는지
|
|
21
|
+
|
|
22
|
+
**소셜 공유**
|
|
23
|
+
- OG 이미지 설정 여부 (og:image)
|
|
24
|
+
- Twitter Card 설정 여부
|
|
25
|
+
- OG 이미지가 정적 파일인지 동적 생성인지
|
|
26
|
+
|
|
27
|
+
**구조화 데이터**
|
|
28
|
+
- JSON-LD script 태그 여부
|
|
29
|
+
- 앱 유형에 맞는 schema 적용 여부
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Step 2: 결과 리포트
|
|
34
|
+
|
|
35
|
+
아래 형식으로 현황을 출력한다:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
SEO 감사 결과
|
|
39
|
+
|
|
40
|
+
✓ 완료된 항목
|
|
41
|
+
✓ ...
|
|
42
|
+
|
|
43
|
+
⚠ 미흡한 항목
|
|
44
|
+
⚠ ...
|
|
45
|
+
|
|
46
|
+
✗ 없는 항목
|
|
47
|
+
✗ app/sitemap.ts — 없음
|
|
48
|
+
✗ OG 이미지 — 없음
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
SEO 점수: [완료] / [전체] 항목
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Step 3: 보완 여부 확인
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
위 항목들을 자동으로 보완할까요?
|
|
60
|
+
(1) 전체 자동 적용
|
|
61
|
+
(2) 항목 선택 후 적용
|
|
62
|
+
(3) 리포트만 보기
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
선택에 따라 `@m1kapp/kit/seo`의 유틸로 빠진 항목을 채운다.
|
|
66
|
+
기존 코드는 보존하고 필요한 부분만 추가/수정한다.
|
package/bin/skills.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @m1kapp/kit skills installer
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx m1kkit skills — 전체 스킬 설치
|
|
7
|
+
* npx m1kkit skills --list — 설치 가능한 스킬 목록
|
|
8
|
+
* npx m1kkit skills m1kapp-init — 특정 스킬만 설치
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const SKILLS_SRC = path.join(__dirname, "skills");
|
|
17
|
+
|
|
18
|
+
const SKILLS = [
|
|
19
|
+
{
|
|
20
|
+
name: "m1kapp-init",
|
|
21
|
+
desc: "/m1kapp-init — Next.js 프로젝트 초기 설정 인터랙티브 스캐폴딩",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "m1kapp-seo",
|
|
25
|
+
desc: "/m1kapp-seo — SEO 감사 및 @m1kapp/kit/seo 자동 적용",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "m1kapp-pwa",
|
|
29
|
+
desc: "/m1kapp-pwa — PWA 설정 점검 및 @m1kapp/kit/pwa 자동 적용",
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const listOnly = args.includes("--list");
|
|
35
|
+
const target = args.find((a) => !a.startsWith("--"));
|
|
36
|
+
|
|
37
|
+
// 설치 대상 디렉토리: 프로젝트 로컬 우선, 없으면 글로벌
|
|
38
|
+
function getSkillsDir() {
|
|
39
|
+
const local = path.join(process.cwd(), ".claude", "skills");
|
|
40
|
+
const global = path.join(
|
|
41
|
+
process.env.HOME ?? process.env.USERPROFILE ?? "~",
|
|
42
|
+
".claude",
|
|
43
|
+
"skills"
|
|
44
|
+
);
|
|
45
|
+
// 프로젝트에 .claude 폴더가 이미 있으면 로컬, 아니면 글로벌
|
|
46
|
+
const hasLocalClaude = fs.existsSync(path.join(process.cwd(), ".claude"));
|
|
47
|
+
return hasLocalClaude ? local : global;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (listOnly) {
|
|
51
|
+
console.log("\n사용 가능한 @m1kapp/kit 스킬:\n");
|
|
52
|
+
for (const s of SKILLS) {
|
|
53
|
+
console.log(` ${s.desc}`);
|
|
54
|
+
}
|
|
55
|
+
console.log("\n설치: npx m1kkit skills\n");
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const toInstall = target
|
|
60
|
+
? SKILLS.filter((s) => s.name === target)
|
|
61
|
+
: SKILLS;
|
|
62
|
+
|
|
63
|
+
if (target && toInstall.length === 0) {
|
|
64
|
+
console.error(`스킬을 찾을 수 없습니다: ${target}`);
|
|
65
|
+
console.error(`사용 가능: ${SKILLS.map((s) => s.name).join(", ")}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const skillsDir = getSkillsDir();
|
|
70
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
console.log(`\n@m1kapp/kit 스킬 설치 중...\n`);
|
|
73
|
+
console.log(`설치 위치: ${skillsDir}\n`);
|
|
74
|
+
|
|
75
|
+
let installed = 0;
|
|
76
|
+
for (const skill of toInstall) {
|
|
77
|
+
const src = path.join(SKILLS_SRC, `${skill.name}.md`);
|
|
78
|
+
const destDir = path.join(skillsDir, skill.name);
|
|
79
|
+
const dest = path.join(destDir, "skill.md");
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(src)) {
|
|
82
|
+
console.log(` ⚠ ${skill.name} — 소스 파일 없음 (스킵)`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
87
|
+
fs.copyFileSync(src, dest);
|
|
88
|
+
console.log(` ✓ /${skill.name}`);
|
|
89
|
+
installed++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`\n${installed}개 스킬 설치 완료!\n`);
|
|
93
|
+
console.log("Claude Code에서 바로 사용하세요:\n");
|
|
94
|
+
for (const s of toInstall) {
|
|
95
|
+
console.log(` /${s.name}`);
|
|
96
|
+
}
|
|
97
|
+
console.log();
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import * as React from 'react';
|
|
2
|
+
import * as React$1 from 'react';
|
|
3
3
|
import React__default, { ReactNode, CSSProperties } from 'react';
|
|
4
4
|
import { MetadataRoute, Viewport } from 'next';
|
|
5
5
|
import { ClassValue } from 'clsx';
|
|
@@ -214,6 +214,12 @@ interface ThemeButtonProps {
|
|
|
214
214
|
* - Without `color`: moon (light mode) or sun (dark mode) icon.
|
|
215
215
|
*/
|
|
216
216
|
declare function ThemeButton({ color, dark: darkProp, onClick, className }: ThemeButtonProps): react_jsx_runtime.JSX.Element;
|
|
217
|
+
interface ThemeDialogLabels {
|
|
218
|
+
title?: string;
|
|
219
|
+
light?: string;
|
|
220
|
+
dark?: string;
|
|
221
|
+
close?: string;
|
|
222
|
+
}
|
|
217
223
|
interface ThemeDialogProps {
|
|
218
224
|
open: boolean;
|
|
219
225
|
onClose: () => void;
|
|
@@ -223,12 +229,14 @@ interface ThemeDialogProps {
|
|
|
223
229
|
onDarkToggle?: () => void;
|
|
224
230
|
/** Override the color palette. Defaults to built-in colors. */
|
|
225
231
|
palette?: Record<string, string>;
|
|
232
|
+
/** Override default Korean labels for i18n */
|
|
233
|
+
labels?: ThemeDialogLabels;
|
|
226
234
|
}
|
|
227
235
|
/**
|
|
228
236
|
* Bottom-sheet style color picker dialog.
|
|
229
237
|
* Shows a 5-column grid of color circles with check on the active one.
|
|
230
238
|
*/
|
|
231
|
-
declare function ThemeDialog({ open, onClose, current, onSelect, dark: darkProp, onDarkToggle, palette, }: ThemeDialogProps):
|
|
239
|
+
declare function ThemeDialog({ open, onClose, current, onSelect, dark: darkProp, onDarkToggle, palette, labels: _labels, }: ThemeDialogProps): react_jsx_runtime.JSX.Element;
|
|
232
240
|
|
|
233
241
|
/**
|
|
234
242
|
* Font presets for @m1kapp/ui.
|
|
@@ -324,16 +332,22 @@ interface EmojiButtonProps {
|
|
|
324
332
|
* Use it anywhere — tab icons, headers, list items, etc.
|
|
325
333
|
*/
|
|
326
334
|
declare function EmojiButton({ emoji, onClick, className }: EmojiButtonProps): react_jsx_runtime.JSX.Element;
|
|
335
|
+
interface EmojiPickerLabels {
|
|
336
|
+
title?: string;
|
|
337
|
+
close?: string;
|
|
338
|
+
}
|
|
327
339
|
interface EmojiPickerProps {
|
|
328
340
|
open: boolean;
|
|
329
341
|
onClose: () => void;
|
|
330
342
|
current: string;
|
|
331
343
|
onSelect: (emoji: string) => void;
|
|
344
|
+
/** Override default Korean labels for i18n */
|
|
345
|
+
labels?: EmojiPickerLabels;
|
|
332
346
|
}
|
|
333
347
|
/**
|
|
334
348
|
* Bottom-sheet emoji picker with categories.
|
|
335
349
|
*/
|
|
336
|
-
declare function EmojiPicker({ open, onClose, current, onSelect }: EmojiPickerProps):
|
|
350
|
+
declare function EmojiPicker({ open, onClose, current, onSelect, labels: _labels }: EmojiPickerProps): react_jsx_runtime.JSX.Element;
|
|
337
351
|
|
|
338
352
|
interface TooltipProps {
|
|
339
353
|
label: string;
|
|
@@ -356,14 +370,24 @@ interface GrassMapData {
|
|
|
356
370
|
date: string;
|
|
357
371
|
count: number;
|
|
358
372
|
}
|
|
373
|
+
interface GrassMapLabels {
|
|
374
|
+
firstRecord?: string;
|
|
375
|
+
noRecord?: string;
|
|
376
|
+
today?: string;
|
|
377
|
+
less?: string;
|
|
378
|
+
more?: string;
|
|
379
|
+
first?: string;
|
|
380
|
+
}
|
|
359
381
|
interface GrassMapProps {
|
|
360
382
|
data: GrassMapData[];
|
|
361
383
|
accent: string;
|
|
362
384
|
isDark?: boolean;
|
|
363
385
|
/** Unit label appended to count in tooltip. e.g. "명", "commits". Default: "" */
|
|
364
386
|
unit?: string;
|
|
387
|
+
/** Override default Korean labels for i18n */
|
|
388
|
+
labels?: GrassMapLabels;
|
|
365
389
|
}
|
|
366
|
-
declare function GrassMap({ data, accent, isDark, unit }: GrassMapProps): react_jsx_runtime.JSX.Element;
|
|
390
|
+
declare function GrassMap({ data, accent, isDark, unit, labels: _labels }: GrassMapProps): react_jsx_runtime.JSX.Element;
|
|
367
391
|
|
|
368
392
|
type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
369
393
|
type AvatarShape = "circle" | "rounded";
|
|
@@ -503,6 +527,15 @@ interface UseInViewResult {
|
|
|
503
527
|
*/
|
|
504
528
|
declare function useInView(options?: UseInViewOptions): UseInViewResult;
|
|
505
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Traps keyboard focus inside a container while active.
|
|
532
|
+
* Tab / Shift+Tab cycle through focusable elements without escaping.
|
|
533
|
+
*
|
|
534
|
+
* @param active — whether the trap is currently engaged
|
|
535
|
+
* @returns ref to attach to the container element
|
|
536
|
+
*/
|
|
537
|
+
declare function useFocusTrap<T extends HTMLElement = HTMLElement>(active: boolean): React$1.RefObject<T | null>;
|
|
538
|
+
|
|
506
539
|
interface SkeletonProps {
|
|
507
540
|
/** Tailwind classes for width / height (e.g. "h-4 w-3/4") */
|
|
508
541
|
className?: string;
|
|
@@ -533,26 +566,21 @@ interface DialogProps {
|
|
|
533
566
|
children: React__default.ReactNode;
|
|
534
567
|
/** Hide the backdrop click-to-close behaviour */
|
|
535
568
|
persistent?: boolean;
|
|
569
|
+
/** Close button aria-label. Default: "닫기" */
|
|
570
|
+
closeLabel?: string;
|
|
536
571
|
className?: string;
|
|
537
572
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
* <div className="flex gap-2 mt-4">
|
|
550
|
-
* <Button onClick={handleDelete}>삭제</Button>
|
|
551
|
-
* <Button onClick={() => setOpen(false)}>취소</Button>
|
|
552
|
-
* </div>
|
|
553
|
-
* </Dialog>
|
|
554
|
-
*/
|
|
555
|
-
declare function Dialog({ open, onClose, title, size, children, persistent, className, }: DialogProps): React__default.ReactPortal | null;
|
|
573
|
+
declare function Dialog({ open, onClose, title, size, children, persistent, closeLabel, className, }: DialogProps): react_jsx_runtime.JSX.Element;
|
|
574
|
+
|
|
575
|
+
interface InAppSheetProps {
|
|
576
|
+
open: boolean;
|
|
577
|
+
onClose: () => void;
|
|
578
|
+
children: React.ReactNode;
|
|
579
|
+
className?: string;
|
|
580
|
+
/** true면 시트가 AppShell 전체 높이를 채움 */
|
|
581
|
+
fullHeight?: boolean;
|
|
582
|
+
}
|
|
583
|
+
declare function InAppSheet({ open, onClose, children, className, fullHeight, }: InAppSheetProps): react_jsx_runtime.JSX.Element;
|
|
556
584
|
|
|
557
585
|
type PWAInstallState = "android-ready" | "ios-safari" | "installed" | "unsupported";
|
|
558
586
|
interface UsePWAInstallReturn {
|
|
@@ -767,4 +795,4 @@ declare function formatPrice(amount: number, currency?: string, locale?: string)
|
|
|
767
795
|
|
|
768
796
|
declare function cn(...inputs: ClassValue[]): string;
|
|
769
797
|
|
|
770
|
-
export { type ApiClient, type ApiClientOptions, ApiError, AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, type ColorName, Dialog, type DialogProps, Divider, EmojiButton, type EmojiButtonProps, EmojiPicker, type EmojiPickerProps, EmptyState, type EmptyStateProps, type FontName, GrassMap, type GrassMapData, type GrassMapProps, IOSInstallSheet, KitStyles, PWAInstallButton, type PWAInstallState, Section, SectionHeader, type SectionHeaderProps, type SectionProps, ShareButton, type ShareButtonProps, Skeleton, type SkeletonProps, StatChip, type StatChipProps, THEME_SCRIPT, Tab, TabBar, type TabBarProps, type TabProps, ThemeButton, type ThemeButtonProps, ThemeDialog, type ThemeDialogProps, type ToastOptions, ToastProvider, type ToastVariant, Tooltip, type TooltipProps, Typewriter, type TypewriterProps, type UseFetchOptions, type UseFetchResult, type UseFormSubmitOptions, type UseFormSubmitResult, type UseInViewOptions, type UseInViewResult, type UsePWAInstallReturn, type UsePollingOptions, type UsePollingResult, type UseShareOptions, type UseShareReturn, Watermark, type WatermarkProps, type WatermarkSponsor, clearFetchCache, cn, colors, createApiClient, createManifest, fontFamily, fonts, formatNumber, formatPrice, mobileViewport, relativeTime, svgIcon, useDebounce, useFetch, useFormSubmit, useInView, useLocalStorage, usePWAInstall, usePolling, useShare, useToast };
|
|
798
|
+
export { type ApiClient, type ApiClientOptions, ApiError, AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, type ColorName, Dialog, type DialogProps, Divider, EmojiButton, type EmojiButtonProps, EmojiPicker, type EmojiPickerLabels, type EmojiPickerProps, EmptyState, type EmptyStateProps, type FontName, GrassMap, type GrassMapData, type GrassMapLabels, type GrassMapProps, IOSInstallSheet, InAppSheet, type InAppSheetProps, KitStyles, PWAInstallButton, type PWAInstallState, Section, SectionHeader, type SectionHeaderProps, type SectionProps, ShareButton, type ShareButtonProps, Skeleton, type SkeletonProps, StatChip, type StatChipProps, THEME_SCRIPT, Tab, TabBar, type TabBarProps, type TabProps, ThemeButton, type ThemeButtonProps, ThemeDialog, type ThemeDialogLabels, type ThemeDialogProps, type ToastOptions, ToastProvider, type ToastVariant, Tooltip, type TooltipProps, Typewriter, type TypewriterProps, type UseFetchOptions, type UseFetchResult, type UseFormSubmitOptions, type UseFormSubmitResult, type UseInViewOptions, type UseInViewResult, type UsePWAInstallReturn, type UsePollingOptions, type UsePollingResult, type UseShareOptions, type UseShareReturn, Watermark, type WatermarkProps, type WatermarkSponsor, clearFetchCache, cn, colors, createApiClient, createManifest, fontFamily, fonts, formatNumber, formatPrice, mobileViewport, relativeTime, svgIcon, useDebounce, useFetch, useFocusTrap, useFormSubmit, useInView, useLocalStorage, usePWAInstall, usePolling, useShare, useToast };
|