reborn-vibe4 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 ADDED
@@ -0,0 +1,37 @@
1
+ # reborn-vibe4 — 바이브 코딩 전에 깔 플러그인 넷 안내 스킬
2
+
3
+ 클로드 코드로 바이브 코딩을 시작하기 전에 깔 만한 **Supabase 플러그인 · Vercel 플러그인 · Oh My Claude Code · Chrome DevTools MCP** 의
4
+ 공식 설치 명령과, 붙이기 전에 알아야 할 것을 먼저 말해 주는 안내 스킬입니다.
5
+
6
+ ```
7
+ npx --yes reborn-vibe4 # 안내 스킬을 깐다
8
+ npx --yes reborn-vibe4 --check # 이 PC 에 넷이 붙어 있는지 본다 (파일만 읽는다)
9
+ ```
10
+
11
+ 깔리는 자리 `~/.claude/skills/vibe-coding-plugins` · 사실 원장 `skill/facts.json`(확인일 2026-10-01).
12
+
13
+ ★**넷은 깔지도, 대신 실행하지도 않습니다.** 공식 명령을 보여 드리고 사용자가 보고 직접 칩니다.
14
+
15
+ ---
16
+
17
+ ## 먼저 아셔야 할 셋
18
+
19
+ 1. **Supabase 플러그인이 묶어 온 MCP 에는 `read_only` 가 없습니다.** 기본이 쓰기 가능입니다.
20
+ 읽기전용으로 붙이는 줄(공식 문서의 URL 파라미터)을 스킬이 알려 줍니다.
21
+ 2. **플러그인과 MCP 는 안 써도 매 호출 문맥에 실립니다.** 넷을 다 켜지 말고 지금 일에 필요한 것만 켭니다.
22
+ 3. **Chrome DevTools MCP 는 연결된 크롬의 내용을 모델에 노출합니다.** 민감한 탭을 띄우지 않습니다.
23
+
24
+ ## 실측한 값 (2026-10-01)
25
+
26
+ | 도구 | 저장소 | 라이선스 | 설치 |
27
+ |---|---|---|---|
28
+ | Supabase 플러그인 | supabase-community/supabase-plugin | MIT | `/plugin install supabase@claude-plugins-official` |
29
+ | Vercel 플러그인 | vercel/vercel-plugin | Apache-2.0 | `/plugin install vercel@claude-plugins-official` |
30
+ | Oh My Claude Code | Yeachan-Heo/oh-my-claudecode | MIT | `/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode` → `/plugin install oh-my-claudecode` |
31
+ | Chrome DevTools MCP | ChromeDevTools/chrome-devtools-mcp | Apache-2.0 | `claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest` |
32
+
33
+ GitHub API · 각 저장소 README/LICENSE · Anthropic 공식 마켓플레이스 목록 · supabase.com 공식 문서로 직접 확인했습니다.
34
+ 이 PC 에서 넷을 실제로 깔아 본 것은 아닙니다.
35
+
36
+ 넷은 각각 다른 회사·프로젝트가 만든 것이며 **리본랩스와 제휴 관계가 없습니다.**
37
+ 이 안내 스킬과 설치기는 무료이며, 각 도구의 요금은 적지 않았습니다(바뀌는 값).
package/install.mjs ADDED
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * reborn-vibe4 — 바이브 코딩 전에 깔 플러그인 넷 안내 스킬 설치기
4
+ *
5
+ * npx --yes reborn-vibe4 ~/.claude/skills/vibe-coding-plugins/ 에 안내 스킬을 깐다
6
+ * npx --yes reborn-vibe4 --check 이 PC 에 넷이 붙어 있는지 본다(파일만 읽는다 · 아무것도 안 고친다)
7
+ * npx --yes reborn-vibe4 --self-test 스스로 시험한다
8
+ *
9
+ * 규율:
10
+ * · 사실은 skill/facts.json 에서만 온다. 여기서 짓지 않는다.
11
+ * · ★넷(Supabase · Vercel · Oh My Claude Code · Chrome DevTools MCP)은 깔지도 · 덮지도 · 지우지도 않는다.
12
+ * 사용자 PC 에 이미 붙어 있을 수 있다. 우리는 공식 명령을 보여 줄 뿐이고, 사용자가 보고 직접 친다.
13
+ * · --check 는 ~/.claude.json · ~/.claude/plugins/installed_plugins.json · 현재 폴더 .mcp.json 을 읽기만 한다.
14
+ */
15
+ import { readFileSync, readdirSync, mkdirSync, existsSync, statSync, cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
16
+ import { join, dirname } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { homedir, tmpdir } from "node:os";
19
+
20
+ const 이파일 = fileURLToPath(import.meta.url);
21
+ const HERE = dirname(이파일);
22
+ const F = JSON.parse(readFileSync(join(HERE, "skill", "facts.json"), "utf8"));
23
+ const 스킬이름 = "vibe-coding-plugins";
24
+ const 표식 = "reborn-vibe4:guide";
25
+ const 퍼널 = "vibe4";
26
+ const 인수 = process.argv.slice(2);
27
+ const 있나 = (f) => 인수.includes(f);
28
+
29
+ const C = {
30
+ 진: (s) => `\x1b[1m${s}\x1b[0m`,
31
+ 흐: (s) => `\x1b[2m${s}\x1b[0m`,
32
+ 코: (s) => `\x1b[38;5;209m${s}\x1b[0m`,
33
+ 빨: (s) => `\x1b[38;5;203m${s}\x1b[0m`,
34
+ 초: (s) => `\x1b[38;5;114m${s}\x1b[0m`,
35
+ 노: (s) => `\x1b[38;5;221m${s}\x1b[0m`,
36
+ };
37
+
38
+ const 도구 = (이름) => F.도구.find((c) => c.이름 === 이름);
39
+
40
+ /* ══ 이 PC 에 붙어 있나 — 파일만 읽는다 ═════════════════════════════ */
41
+
42
+ function 읽기(경로) {
43
+ try { return { 값: JSON.parse(readFileSync(경로, "utf8")), 깨짐: false }; }
44
+ catch (e) { return { 값: null, 깨짐: e.code !== "ENOENT" }; }
45
+ }
46
+
47
+ /** 플러그인 키 「이름@마켓」 에서 이름이 정확히 같은 것만 센다. (vercel-deploy 가 vercel 로 잡히면 안 된다) */
48
+ function 플러그인있나(플러그인들, 이름) {
49
+ return Object.keys(플러그인들 || {}).filter((k) => k.split("@")[0] === 이름);
50
+ }
51
+
52
+ /** MCP 서버를 모은다: ~/.claude.json 최상위 + projects[*] + 현재 폴더 .mcp.json */
53
+ function MCP모으기(집, 폴더) {
54
+ const 목록 = [];
55
+ const 깨진 = [];
56
+ const 담기 = (서버들, 범위) => {
57
+ for (const [이름, s] of Object.entries(서버들 || {})) 목록.push({ 이름, 범위, 설정: s || {} });
58
+ };
59
+ const 홈 = 읽기(join(집, ".claude.json"));
60
+ if (홈.깨짐) 깨진.push(".claude.json");
61
+ if (홈.값) {
62
+ 담기(홈.값.mcpServers, "사용자");
63
+ for (const [경로, p] of Object.entries(홈.값.projects || {})) 담기(p?.mcpServers, "프로젝트 " + 경로);
64
+ }
65
+ const 프로젝트 = 읽기(join(폴더, ".mcp.json"));
66
+ if (프로젝트.깨짐) 깨진.push(".mcp.json");
67
+ if (프로젝트.값) 담기(프로젝트.값.mcpServers, "이 폴더 .mcp.json");
68
+ return { 목록, 깨진 };
69
+ }
70
+
71
+ const 글 = (s) => JSON.stringify(s?.설정 ?? {});
72
+
73
+ /**
74
+ * ★순수 함수 — 집·폴더를 받아 네 칸을 돌려준다. 시험이 가짜 집으로 양방향을 잰다.
75
+ * 읽기만 한다. 어떤 파일도 쓰지 않는다.
76
+ */
77
+ export function 점검(집 = homedir(), 폴더 = process.cwd()) {
78
+ const 설치 = 읽기(join(집, ".claude", "plugins", "installed_plugins.json"));
79
+ const 플러그인들 = 설치.값?.plugins || {};
80
+ const { 목록: mcp, 깨진 } = MCP모으기(집, 폴더);
81
+ if (설치.깨짐) 깨진.push("installed_plugins.json");
82
+
83
+ const 결과 = {};
84
+ /* ① Supabase — 플러그인 또는 원격 MCP(mcp.supabase.com) */
85
+ {
86
+ const p = 플러그인있나(플러그인들, "supabase");
87
+ const m = mcp.filter((s) => /mcp\.supabase\.com/.test(글(s)));
88
+ const 읽기전용들 = m.map((s) => /[?&]read_only=true(&|"|$)/.test(글(s)));
89
+ 결과["Supabase 플러그인"] = {
90
+ 설치: p.length > 0 || m.length > 0,
91
+ 어디: [...p.map((k) => "플러그인 " + k), ...m.map((s) => `MCP ${s.이름} (${s.범위})`)],
92
+ 플러그인: p.length > 0,
93
+ MCP: m.length > 0,
94
+ 읽기전용: m.length ? 읽기전용들.every(Boolean) : null,
95
+ };
96
+ }
97
+ /* ② Vercel — 플러그인 */
98
+ {
99
+ const p = 플러그인있나(플러그인들, "vercel");
100
+ 결과["Vercel 플러그인"] = { 설치: p.length > 0, 어디: p.map((k) => "플러그인 " + k) };
101
+ }
102
+ /* ③ Oh My Claude Code — 플러그인 */
103
+ {
104
+ const p = 플러그인있나(플러그인들, "oh-my-claudecode");
105
+ 결과["Oh My Claude Code"] = { 설치: p.length > 0, 어디: p.map((k) => "플러그인 " + k) };
106
+ }
107
+ /* ④ Chrome DevTools MCP — MCP 또는 플러그인 */
108
+ {
109
+ const p = 플러그인있나(플러그인들, "chrome-devtools-mcp");
110
+ const m = mcp.filter((s) => /chrome-devtools-mcp/.test(글(s)));
111
+ 결과["Chrome DevTools MCP"] = {
112
+ 설치: p.length > 0 || m.length > 0,
113
+ 어디: [...p.map((k) => "플러그인 " + k), ...m.map((s) => `MCP ${s.이름} (${s.범위})`)],
114
+ };
115
+ }
116
+ return { 결과, 깨진 };
117
+ }
118
+
119
+ function 점검보고(집 = homedir(), 폴더 = process.cwd()) {
120
+ const { 결과, 깨진 } = 점검(집, 폴더);
121
+ console.log("");
122
+ console.log(C.진("이 PC 에 붙어 있나") + C.흐(" — 파일만 읽었습니다. 아무것도 깔거나 고치지 않았습니다."));
123
+ let 붙음 = 0;
124
+ for (const d of F.도구) {
125
+ const r = 결과[d.이름];
126
+ if (r.설치) {
127
+ 붙음++;
128
+ console.log(` ${C.초("✓")} ${d.이름} ${C.흐(r.어디.join(" · "))}`);
129
+ if (d.이름 === "Supabase 플러그인") {
130
+ if (r.MCP && r.읽기전용 === true) console.log(C.흐(" MCP 주소에 read_only=true 가 있습니다."));
131
+ else if (r.MCP && r.읽기전용 === false) console.log(" " + C.노("! MCP 주소에 read_only=true 가 없습니다 — 쓰기가 열려 있습니다."));
132
+ if (r.플러그인) console.log(" " + C.노("! 플러그인이 묶어 온 MCP 에는 읽기전용 표시가 없습니다(기본이 쓰기 가능)."));
133
+ if (r.플러그인 || r.읽기전용 === false) {
134
+ console.log(C.흐(" 읽기전용으로 붙이려면:"));
135
+ console.log(" " + C.코(d.읽기전용));
136
+ }
137
+ }
138
+ } else {
139
+ console.log(` ${C.빨("✗")} ${d.이름} ${C.흐("— 없음")}`);
140
+ }
141
+ }
142
+ if (깨진.length) console.log(C.노(`\n ! 읽다가 깨진 파일: ${깨진.join(", ")} — 그 칸은 「없음」으로 셌습니다.`));
143
+ console.log(C.흐(`\n ${붙음}/${F.도구.length} 붙어 있습니다.`));
144
+ console.log(C.흐(" 여기서 보이는 것은 이 PC 의 사용자 설정과 현재 폴더 .mcp.json 뿐입니다. 다른 폴더의 프로젝트 설정은 그 폴더에서 다시 보세요."));
145
+ console.log("");
146
+ return 결과;
147
+ }
148
+
149
+ /* ══ 설치 ══════════════════════════════════════════════════════════ */
150
+
151
+ function 깔기(집 = homedir(), { 조용히 = false } = {}) {
152
+ const 스킬들 = join(집, ".claude", "skills");
153
+ const 밖 = join(스킬들, 스킬이름);
154
+ const 기존 = join(밖, "SKILL.md");
155
+
156
+ /* ★남의 스킬을 덮지 않는다 — 같은 이름 폴더에 우리 표식이 없으면 멈춘다 */
157
+ if (existsSync(기존) && !readFileSync(기존, "utf8").includes(표식)) {
158
+ if (!조용히) {
159
+ console.log(C.빨(`\n ✗ ${밖} 에 다른 스킬이 이미 있습니다 — 덮어쓰지 않았습니다.`));
160
+ console.log(C.흐(" 그 폴더를 옮기거나 지운 뒤 다시 실행하세요.\n"));
161
+ }
162
+ process.exitCode = 1;
163
+ return false;
164
+ }
165
+
166
+ mkdirSync(밖, { recursive: true });
167
+ cpSync(join(HERE, "skill"), 밖, { recursive: true });
168
+
169
+ /* ★깔았다고 말하기 전에 실제로 읽히는지 본다. 파일 복사는 조용히 실패한다. */
170
+ const 확인 = [기존, join(밖, "facts.json")];
171
+ const 빠진 = 확인.filter((p) => !existsSync(p) || statSync(p).size < 200);
172
+ if (빠진.length) {
173
+ if (!조용히) {
174
+ console.log(C.빨("\n ✗ 깔다가 말았습니다 — 다음 파일이 비었습니다:"));
175
+ for (const p of 빠진) console.log(" " + p);
176
+ }
177
+ process.exitCode = 1;
178
+ return false;
179
+ }
180
+ if (조용히) return 밖;
181
+
182
+ console.log("");
183
+ console.log(C.초(" ✓ 깔렸습니다") + C.흐(` → ${밖}`));
184
+ console.log("");
185
+ console.log(" 지금 이 PC 에 뭐가 붙어 있는지부터 보세요:");
186
+ console.log(C.코(" npx --yes reborn-vibe4 --check"));
187
+ console.log("");
188
+ console.log(" 넷의 공식 설치 명령입니다 — " + C.진("보고 직접 치세요. 대신 깔지 않습니다."));
189
+ console.log(C.흐(" / 로 시작하는 줄은 클로드 코드 안에서, claude mcp add 는 터미널에서 칩니다."));
190
+ for (const d of F.도구) {
191
+ console.log("");
192
+ console.log(` ${C.진(d.번호 + ". " + d.이름)} ${C.흐(`${d.저장소} · ${d.라이선스}`)}`);
193
+ for (const 줄 of d.명령) console.log(" " + C.코(줄));
194
+ if (d.명령메모) console.log(C.흐(" " + d.명령메모));
195
+ }
196
+ console.log("");
197
+ console.log(" " + C.노("★ Supabase 는 쓰기 권한부터 막으세요") + C.흐(" — 플러그인이 묶어 온 MCP 에는 읽기전용 표시가 없습니다(기본이 쓰기 가능)."));
198
+ console.log(C.흐(" 읽기전용으로 붙이는 줄:"));
199
+ console.log(" " + C.코(도구("Supabase 플러그인").읽기전용));
200
+ console.log(" " + C.노("★ 넷을 다 켜지 마세요") + C.흐(" — 플러그인과 MCP 는 안 써도 매 호출 문맥에 실립니다."));
201
+ console.log("");
202
+ console.log(" 클로드 코드를 " + C.진("다시 켜면") + " 안내가 바로 잡힙니다. 이렇게 말해 보세요:");
203
+ console.log("");
204
+ console.log(C.코(" 바이브 코딩 전에 뭘 깔아야 해?"));
205
+ console.log(C.코(" supabase 플러그인 붙이는데 DB 를 지울까 봐 무서워"));
206
+ console.log("");
207
+ return 밖;
208
+ }
209
+
210
+ /* ══ 끝난 뒤 한 칸 ═════════════════════════════════════════════════ */
211
+ async function 끝한칸() {
212
+ try {
213
+ const { TAIL, 링크, 마지막 } = await import("./tail.mjs");
214
+ const t = TAIL[퍼널];
215
+ const 굵게 = (s) => s.replace(/\*\*(.+?)\*\*/g, (_, x) => `\x1b[0m\x1b[1m${x}\x1b[0m\x1b[2m`);
216
+ console.log(C.흐(" ────────────────────────────────────────────────"));
217
+ for (const 줄 of t.이음) console.log(C.흐(" " + 굵게(줄)));
218
+ console.log("");
219
+ console.log(" " + C.진(t.제목) + C.흐(" — " + t.한줄));
220
+ console.log(" " + C.코(링크(퍼널)));
221
+ console.log(C.흐(" " + 마지막));
222
+ console.log("");
223
+ } catch { /* 안내가 없어도 설치는 끝난 것이다 */ }
224
+ }
225
+
226
+ /* ══ 자가시험 ══════════════════════════════════════════════════════ */
227
+ async function 자가시험() {
228
+ const 나쁨 = [];
229
+ let 건 = 0;
230
+ const T = (이름, 참) => { 건++; if (!참) 나쁨.push(이름); };
231
+
232
+ /* ① 사실 원장 */
233
+ T("facts.json 에 확인일이 없다", /^\d{4}-\d{2}-\d{2}$/.test(F.확인일 || ""));
234
+ T("도구가 4종이 아니다", Array.isArray(F.도구) && F.도구.length === 4);
235
+ const 이름들 = F.도구.map((c) => c.이름).join(",");
236
+ T(`도구 이름·순서가 샘플(Supabase→Vercel→OMC→DevTools)과 다르다 (${이름들})`, 이름들 === "Supabase 플러그인,Vercel 플러그인,Oh My Claude Code,Chrome DevTools MCP");
237
+ for (const [이름, 저장소, 라, 명령] of [
238
+ ["Supabase 플러그인", "supabase-community/supabase-plugin", "MIT", "/plugin install supabase@claude-plugins-official"],
239
+ ["Vercel 플러그인", "vercel/vercel-plugin", "Apache-2.0", "/plugin install vercel@claude-plugins-official"],
240
+ ["Oh My Claude Code", "Yeachan-Heo/oh-my-claudecode", "MIT", "/plugin install oh-my-claudecode"],
241
+ ["Chrome DevTools MCP", "ChromeDevTools/chrome-devtools-mcp", "Apache-2.0", "claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest"],
242
+ ]) {
243
+ const c = 도구(이름);
244
+ T(`${이름} 칸이 없다`, !!c);
245
+ T(`${이름} 저장소가 실측(${저장소})과 다르다`, c?.저장소 === 저장소);
246
+ T(`${이름} 라이선스가 실측(${라})과 다르다`, c?.라이선스 === 라);
247
+ T(`${이름} 공식 설치 명령(${명령})이 없다`, (c?.명령 || []).includes(명령));
248
+ T(`${이름} 별에 숫자가 없다`, Number.isFinite(c?.별));
249
+ }
250
+ T("OMC 설치가 두 줄(마켓 추가 → 설치)이 아니다", 도구("Oh My Claude Code").명령.length === 2 && 도구("Oh My Claude Code").명령[0].startsWith("/plugin marketplace add "));
251
+ T("OMC 에이전트 수 근거가 없다", /19명/.test(도구("Oh My Claude Code").하는일));
252
+ T("Supabase 읽기전용 줄에 read_only=true 가 없다", /read_only=true/.test(도구("Supabase 플러그인").읽기전용));
253
+ T("Supabase 읽기전용 줄에 project_ref 가 없다", /project_ref=/.test(도구("Supabase 플러그인").읽기전용));
254
+ T("Supabase 주의가 「읽기전용 표시 없음」을 말하지 않는다", /read_only 가 없다/.test(도구("Supabase 플러그인").주의));
255
+ T("귀속에 제휴 관계 없음이 없다", /제휴 관계가 없습니다/.test(F.귀속?.말 || ""));
256
+ T("토큰 안내가 없다", /문맥/.test(F.토큰 || ""));
257
+ T("확인 못한 것 칸이 비었다", Array.isArray(F.확인_못한것) && F.확인_못한것.length >= 2);
258
+
259
+ /* ② 문서 */
260
+ const 문서 = {};
261
+ for (const n of ["skill/SKILL.md", "README.md", "안내스킬.md"]) {
262
+ try { 문서[n] = readFileSync(join(HERE, n), "utf8"); } catch { 나쁨.push(`${n} 을 못 읽는다`); 문서[n] = ""; }
263
+ }
264
+ const S = 문서["skill/SKILL.md"];
265
+ T("SKILL.md name 이 폴더 이름과 다르다", new RegExp(`^name:\\s*${스킬이름}\\s*$`, "m").test(S));
266
+ T("SKILL.md 에 덮어쓰기 표식이 없다", S.includes(표식));
267
+ for (const [말, 뜻] of [
268
+ ["/plugin install supabase@claude-plugins-official", "Supabase 설치 줄"],
269
+ ["/plugin install vercel@claude-plugins-official", "Vercel 설치 줄"],
270
+ ["/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode", "OMC 마켓 추가 줄"],
271
+ ["/plugin install oh-my-claudecode", "OMC 설치 줄"],
272
+ ["claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest", "DevTools 설치 줄"],
273
+ ["read_only=true", "읽기전용 파라미터"],
274
+ ["agents/claude/.mcp.json", "플러그인 번들 MCP 에 read_only 가 없다는 근거"],
275
+ ["프롬프트 인젝션", "공식 문서의 위험 안내"],
276
+ ["매 호출 문맥에 실린다", "토큰 비용"],
277
+ ["--slim", "DevTools 가벼운 모드"],
278
+ ["모델에 노출", "크롬 내용 노출"],
279
+ ["VERCEL_PLUGIN_TELEMETRY=off", "Vercel 텔레메트리 끄기"],
280
+ ["한 번에 붙이면 실패", "OMC 두 줄 주의"],
281
+ ["제휴 관계가 없다", "귀속"],
282
+ ["다시 켜", "재시작 안내"],
283
+ ["reborn-vibe4 --check", "붙은 것 확인법"],
284
+ ]) {
285
+ T(`SKILL.md 가 「${뜻}」을 말하지 않는다`, S.includes(말));
286
+ }
287
+ for (const [n, t] of Object.entries(문서)) {
288
+ for (const 금 of ["클로드킷", "자비스 풀스택", "자비스풀스택", "158,000", "58,000", "일감 진단"]) {
289
+ T(`${n} 이 「${금}」 을 말한다`, !t.includes(금));
290
+ }
291
+ for (const 금 of ["평생 무료", "완전 무료", "공식 파트너", "월 20달러"]) {
292
+ T(`${n} 이 확인 안 한 말 「${금}」 을 한다`, !t.includes(금));
293
+ }
294
+ T(`${n} 이 제휴 관계가 있다고 말한다`, !/제휴\s*관계(가|를)?\s*(있|입니다|이다|맺)/.test(t));
295
+ }
296
+
297
+ /* ③ 패키지 */
298
+ try {
299
+ const PK = JSON.parse(readFileSync(join(HERE, "package.json"), "utf8"));
300
+ T("package.json 이름이 reborn-vibe4 가 아니다", PK.name === "reborn-vibe4");
301
+ T("bin 이 install.mjs 를 가리키지 않는다", PK.bin?.["reborn-vibe4"] === "install.mjs");
302
+ T("package.json files 에 우리 파일 말고 다른 것이 있다", JSON.stringify(PK.files) === JSON.stringify(["install.mjs", "tail.mjs", "skill/", "README.md", "안내스킬.md"]));
303
+ T("homepage 에 utm_campaign=vibe4 가 없다", String(PK.homepage).includes("utm_campaign=vibe4"));
304
+ const 스킬파일 = readdirSync(join(HERE, "skill"));
305
+ T(`skill/ 에 SKILL.md·facts.json 말고 다른 파일이 있다 (${스킬파일.join(", ")})`, 스킬파일.sort().join(",") === "SKILL.md,facts.json");
306
+ } catch (e) { 나쁨.push("package.json 을 못 읽는다 — " + e.message); }
307
+
308
+ /* ④ 설치기가 남의 명령을 돌리지 않는다 — 자기 소스를 본다(낱말은 쪼개서 적는다: 이 줄이 걸리지 않게) */
309
+ const 자기 = readFileSync(이파일, "utf8");
310
+ for (const 금 of ["child" + "_process", "exec" + "Sync", "spa" + "wn(", "exec" + "File"]) {
311
+ T(`설치기가 「${금}」 을 쓴다 — 남의 명령을 돌리지 않는다`, !자기.includes(금));
312
+ }
313
+
314
+ /* ⑤ --check 점검 — 가짜 집으로 양방향 */
315
+ const 임시들 = [];
316
+ const 원래코드 = process.exitCode;
317
+ const 집만들기 = (이름, 파일들) => {
318
+ const 집 = mkdtempSync(join(tmpdir(), `reborn-vibe4-${이름}-`));
319
+ 임시들.push(집);
320
+ for (const [상대, 값] of Object.entries(파일들)) {
321
+ const 경로 = join(집, ...상대.split("/"));
322
+ mkdirSync(dirname(경로), { recursive: true });
323
+ writeFileSync(경로, typeof 값 === "string" ? 값 : JSON.stringify(값));
324
+ }
325
+ return 집;
326
+ };
327
+ const 빈폴더 = mkdtempSync(join(tmpdir(), "reborn-vibe4-cwd-"));
328
+ 임시들.push(빈폴더);
329
+ const 플 = (...키) => ({ version: 2, plugins: Object.fromEntries(키.map((k) => [k, [{ scope: "user" }]])) });
330
+ try {
331
+ /* 5-1 빈 집 — 넷 다 없어야 한다(「항상 ✓」인 검사기를 잡는다) */
332
+ const 빈 = 점검(집만들기("empty", {}), 빈폴더);
333
+ T("빈 집인데 설치됨으로 나온다", Object.values(빈.결과).every((r) => r.설치 === false));
334
+ T("빈 집에서 깨진 파일이 보고됐다", 빈.깨진.length === 0);
335
+
336
+ /* 5-2 셋을 깐 집 */
337
+ const 셋 = 점검(집만들기("three", {
338
+ ".claude/plugins/installed_plugins.json": 플("supabase@claude-plugins-official", "vercel@claude-plugins-official", "oh-my-claudecode@omc"),
339
+ }), 빈폴더);
340
+ T("Supabase 플러그인을 못 찾는다", 셋.결과["Supabase 플러그인"].설치 === true && 셋.결과["Supabase 플러그인"].플러그인 === true);
341
+ T("Vercel 플러그인을 못 찾는다", 셋.결과["Vercel 플러그인"].설치 === true);
342
+ T("OMC 를 못 찾는다", 셋.결과["Oh My Claude Code"].설치 === true);
343
+ T("깔지 않은 DevTools 가 있다고 나온다", 셋.결과["Chrome DevTools MCP"].설치 === false);
344
+ T("Supabase 플러그인만 깔렸는데 읽기전용이 참/거짓으로 단정된다(null 이어야 한다)", 셋.결과["Supabase 플러그인"].읽기전용 === null);
345
+
346
+ /* 5-3 이름만 비슷한 것은 세지 않는다 */
347
+ const 비슷 = 점검(집만들기("similar", {
348
+ ".claude/plugins/installed_plugins.json": 플("vercel-deploy@somewhere", "supabase-tools@x", "oh-my-claudecode-extras@x", "chrome-devtools-mcp-lite@x"),
349
+ }), 빈폴더);
350
+ T("이름만 비슷한 플러그인을 센다", Object.values(비슷.결과).every((r) => r.설치 === false));
351
+
352
+ /* 5-4 MCP — DevTools 와 Supabase 읽기전용/쓰기 */
353
+ const 쓰기 = 점검(집만들기("mcp", {
354
+ ".claude.json": {
355
+ mcpServers: {
356
+ "chrome-devtools": { type: "stdio", command: "npx", args: ["chrome-devtools-mcp@latest"] },
357
+ supabase: { type: "http", url: "https://mcp.supabase.com/mcp?project_ref=abc" },
358
+ },
359
+ },
360
+ }), 빈폴더);
361
+ T("DevTools MCP 를 못 찾는다", 쓰기.결과["Chrome DevTools MCP"].설치 === true);
362
+ T("Supabase MCP 를 못 찾는다", 쓰기.결과["Supabase 플러그인"].MCP === true);
363
+ T("read_only 없는 Supabase MCP 가 읽기전용으로 나온다", 쓰기.결과["Supabase 플러그인"].읽기전용 === false);
364
+ const 읽 = 점검(집만들기("ro", {
365
+ ".claude.json": { mcpServers: { supabase: { type: "http", url: "https://mcp.supabase.com/mcp?project_ref=abc&read_only=true" } } },
366
+ }), 빈폴더);
367
+ T("read_only=true 가 있는데 쓰기로 나온다", 읽.결과["Supabase 플러그인"].읽기전용 === true);
368
+ const 거짓 = 점검(집만들기("ro2", {
369
+ ".claude.json": { mcpServers: { supabase: { type: "http", url: "https://mcp.supabase.com/mcp?project_ref=abc&read_only=false" } } },
370
+ }), 빈폴더);
371
+ T("read_only=false 가 읽기전용으로 나온다", 거짓.결과["Supabase 플러그인"].읽기전용 === false);
372
+ const 둘 = 점검(집만들기("ro3", {
373
+ ".claude.json": { mcpServers: {
374
+ a: { type: "http", url: "https://mcp.supabase.com/mcp?read_only=true" },
375
+ b: { type: "http", url: "https://mcp.supabase.com/mcp" },
376
+ } },
377
+ }), 빈폴더);
378
+ T("읽기전용 하나·쓰기 하나인데 읽기전용으로 나온다(하나라도 쓰기면 거짓)", 둘.결과["Supabase 플러그인"].읽기전용 === false);
379
+
380
+ /* 5-5 프로젝트 범위·현재 폴더 .mcp.json */
381
+ const 프로젝트집 = 점검(집만들기("proj", {
382
+ ".claude.json": { projects: { "C:/x": { mcpServers: { cdt: { command: "npx", args: ["-y", "chrome-devtools-mcp@latest", "--slim"] } } } } },
383
+ }), 빈폴더);
384
+ T("프로젝트 범위 MCP 를 못 찾는다", 프로젝트집.결과["Chrome DevTools MCP"].설치 === true);
385
+ const 폴더 = 집만들기("folder", { ".mcp.json": { mcpServers: { supabase: { type: "http", url: "https://mcp.supabase.com/mcp?read_only=true" } } } });
386
+ const 폴더점검 = 점검(집만들기("empty2", {}), 폴더);
387
+ T("현재 폴더 .mcp.json 을 못 읽는다", 폴더점검.결과["Supabase 플러그인"].MCP === true);
388
+
389
+ /* 5-6 깨진 JSON — 던지지 않고 「없음」으로 세고 알린다 */
390
+ let 던짐 = false, 깨짐;
391
+ try { 깨짐 = 점검(집만들기("broken", { ".claude.json": "{ 깨진 json", ".claude/plugins/installed_plugins.json": "[[" }), 빈폴더); } catch { 던짐 = true; }
392
+ T("깨진 JSON 에서 던진다", !던짐);
393
+ T("깨진 JSON 을 알리지 않는다", !던짐 && 깨짐.깨진.length === 2);
394
+ T("깨진 JSON 인데 설치됨으로 나온다", !던짐 && Object.values(깨짐.결과).every((r) => r.설치 === false));
395
+
396
+ /* 5-7 점검은 아무것도 쓰지 않는다 */
397
+ const 읽기집 = 집만들기("readonly", { ".claude.json": { mcpServers: {} }, ".claude/plugins/installed_plugins.json": 플("vercel@x") });
398
+ const 전목록 = readdirSync(읽기집, { recursive: true }).sort().join("|");
399
+ const 전시각 = statSync(join(읽기집, ".claude.json")).mtimeMs;
400
+ 점검(읽기집, 빈폴더);
401
+ T("점검이 파일을 만들거나 지웠다", readdirSync(읽기집, { recursive: true }).sort().join("|") === 전목록);
402
+ T("점검이 파일을 고쳤다", statSync(join(읽기집, ".claude.json")).mtimeMs === 전시각);
403
+
404
+ /* 5-8 깔기 — 빈 집·재설치·이웃 보존·남의 스킬 보호 */
405
+ const 깔집 = mkdtempSync(join(tmpdir(), "reborn-vibe4-inst-"));
406
+ 임시들.push(깔집);
407
+ const 밖 = 깔기(깔집, { 조용히: true });
408
+ T("임시 집에 깔기가 실패했다", !!밖 && existsSync(join(밖, "SKILL.md")) && existsSync(join(밖, "facts.json")));
409
+ T("안내 스킬이 vibe-coding-plugins 폴더가 아닌 곳에 깔렸다", !!밖 && 밖.endsWith(스킬이름));
410
+ T("두 번째 깔기(우리 표식 있음)가 막혔다", !!깔기(깔집, { 조용히: true }));
411
+ T("깔린 facts.json 이 원본과 다르다", readFileSync(join(밖, "facts.json"), "utf8") === readFileSync(join(HERE, "skill", "facts.json"), "utf8"));
412
+
413
+ const 이웃집 = mkdtempSync(join(tmpdir(), "reborn-vibe4-nb-"));
414
+ 임시들.push(이웃집);
415
+ const 이웃 = join(이웃집, ".claude", "skills", "playwright-cli");
416
+ mkdirSync(이웃, { recursive: true });
417
+ writeFileSync(join(이웃, "SKILL.md"), "---\nname: playwright-cli\n---\n이웃 스킬");
418
+ const 전 = { 글: readFileSync(join(이웃, "SKILL.md"), "utf8"), 시각: statSync(join(이웃, "SKILL.md")).mtimeMs };
419
+ T("이웃 있는 집에 깔기가 실패했다", !!깔기(이웃집, { 조용히: true }));
420
+ const 후 = { 글: readFileSync(join(이웃, "SKILL.md"), "utf8"), 시각: statSync(join(이웃, "SKILL.md")).mtimeMs };
421
+ T("이웃 스킬 폴더가 바뀌었다", 전.글 === 후.글 && 전.시각 === 후.시각);
422
+
423
+ const 남 = mkdtempSync(join(tmpdir(), "reborn-vibe4-other-"));
424
+ 임시들.push(남);
425
+ mkdirSync(join(남, ".claude", "skills", 스킬이름), { recursive: true });
426
+ writeFileSync(join(남, ".claude", "skills", 스킬이름, "SKILL.md"), "---\nname: someone-else\n---\n다른 사람 스킬");
427
+ T("남의 스킬을 덮어썼다", 깔기(남, { 조용히: true }) === false && readFileSync(join(남, ".claude", "skills", 스킬이름, "SKILL.md"), "utf8").includes("다른 사람"));
428
+ } catch (e) { 나쁨.push("시험이 던졌다 — " + e.message); }
429
+ finally {
430
+ for (const d of 임시들) rmSync(d, { recursive: true, force: true });
431
+ process.exitCode = 원래코드;
432
+ }
433
+
434
+ /* ⑥ 끝 한 칸 — 스킬팩 한 칸만. ★찍히는 글자만 본다(주석이 걸리는 오탐 방지) */
435
+ try {
436
+ const { TAIL: TT, 마지막: M, 링크: K } = await import("./tail.mjs");
437
+ const t = TT[퍼널];
438
+ T("tail.mjs 에 vibe4 칸이 없다", !!t);
439
+ const 찍히는글 = [...(t?.이음 || []), t?.제목, t?.한줄, M, K(퍼널)].join(" ");
440
+ for (const 금 of ["클로드킷", "자비스 풀스택", "158,000", "58,000", "일감 진단"]) {
441
+ T(`안내 한 칸이 「${금}」 을 말한다 — 무료 설치기는 스킬팩만 말한다`, !찍히는글.includes(금));
442
+ }
443
+ T("안내 한 칸에 스킬팩 값이 없다", 찍히는글.includes("8,900"));
444
+ T("스킬팩 링크에 utm_campaign=vibe4 가 없다", K(퍼널).includes("utm_campaign=vibe4") && K(퍼널).includes("/skillpack?"));
445
+ } catch (e) { 나쁨.push("tail.mjs 를 못 읽는다 — " + e.message); }
446
+
447
+ if (나쁨.length) {
448
+ console.log("✗ 자가시험 " + 나쁨.length + "건 / " + 건);
449
+ for (const n of 나쁨) console.log(" - " + n);
450
+ process.exit(1);
451
+ }
452
+ console.log(`✓ 자가시험 통과 — ${건}건 · 저장소·라이선스·공식 설치 명령 4종 · Supabase 읽기전용 · 문서 필수/금지어 · --check 양방향(빈 집·셋·이름만 비슷·MCP·읽기전용/쓰기·깨진 JSON·읽기만) · 남의 명령 안 돌림 · 임시 집 설치·재설치·이웃 보존·남의 스킬 보호 · 안내 한 칸`);
453
+ }
454
+
455
+ /* ══ ══ */
456
+ /* ★진입점 가드를 두지 않는다 — npx 셈 경로가 가드와 안 맞으면 아무것도 안 하고 exit 0 으로 끝난다. */
457
+ if (있나("--self-test")) {
458
+ await 자가시험();
459
+ } else if (있나("--check")) {
460
+ 점검보고();
461
+ } else if (있나("--help") || 있나("-h")) {
462
+ console.log(`
463
+ ${C.진("reborn-vibe4")} — 바이브 코딩 전에 깔 플러그인 넷 안내 스킬
464
+
465
+ npx --yes reborn-vibe4 안내 스킬을 깐다 (~/.claude/skills/${스킬이름})
466
+ npx --yes reborn-vibe4 --check 이 PC 에 넷이 붙어 있는지 본다(파일만 읽는다)
467
+ npx --yes reborn-vibe4 --self-test 스스로 시험한다
468
+
469
+ Supabase · Vercel · Oh My Claude Code · Chrome DevTools MCP 는 각각 다른 회사·프로젝트가 만든 것이며
470
+ 저희와 제휴 관계가 없습니다. 이 패키지는 그 넷을 깔지도 대신 실행하지도 않습니다. 사실 확인일 ${F.확인일}.
471
+ `);
472
+ } else {
473
+ const 됐다 = 깔기();
474
+ if (됐다) await 끝한칸();
475
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "reborn-vibe4",
3
+ "version": "1.0.0",
4
+ "description": "클로드 코드로 바이브 코딩을 시작하기 전에 깔 만한 플러그인 넷(Supabase 플러그인 · Vercel 플러그인 · Oh My Claude Code · Chrome DevTools MCP)의 공식 설치 명령을 한국어로 안내하는 클로드 코드 스킬을 깝니다. Supabase 플러그인이 묶어 온 MCP 에는 읽기전용 표시가 없다는 점(기본이 쓰기 가능)과 읽기전용으로 붙이는 법, 플러그인·MCP 가 문맥을 먹는다는 점을 먼저 말합니다. --check 는 이 PC 에 넷이 붙어 있는지 파일만 읽어 보여 줍니다. 넷은 각각 다른 회사·프로젝트가 만든 것이며 저희와 제휴 관계가 없고, 이 패키지는 넷을 깔지도 대신 실행하지도 않습니다.",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "plugin",
9
+ "mcp",
10
+ "supabase",
11
+ "vercel",
12
+ "oh-my-claudecode",
13
+ "chrome-devtools",
14
+ "vibe-coding",
15
+ "skill",
16
+ "korean"
17
+ ],
18
+ "homepage": "https://rebornlabs.kr/vibe4?utm_source=installer&utm_medium=cli&utm_campaign=vibe4",
19
+ "bugs": {
20
+ "url": "https://mobility.rebornlabs.kr/cs"
21
+ },
22
+ "license": "MIT",
23
+ "author": "REBORN LABS",
24
+ "type": "module",
25
+ "bin": {
26
+ "reborn-vibe4": "install.mjs"
27
+ },
28
+ "files": [
29
+ "install.mjs",
30
+ "tail.mjs",
31
+ "skill/",
32
+ "README.md",
33
+ "안내스킬.md"
34
+ ],
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "scripts": {
39
+ "test": "node install.mjs --self-test",
40
+ "check": "node install.mjs --check"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: vibe-coding-plugins
3
+ description: 클로드 코드로 바이브 코딩을 시작하기 전에 깔 만한 플러그인 넷(Supabase 플러그인 · Vercel 플러그인 · Oh My Claude Code · Chrome DevTools MCP)의 공식 설치 명령과, 붙이기 전에 알아야 할 위험(Supabase 쓰기 권한 · 토큰 비용 · 크롬 내용 노출)을 말한다. 사용자가 "바이브 코딩 플러그인", "클로드 코드 플러그인 추천", "supabase 플러그인", "vercel 플러그인", "oh my claude code", "chrome devtools mcp", "/plugin install", "claude mcp add", "플러그인 뭐 깔아야 해", "DB 지울까 무서워" 같은 이야기를 할 때 쓴다. 실제 설치·DB 작업·배포는 이 스킬이 아니라 사용자가 붙인 플러그인이 한다.
4
+ ---
5
+
6
+ <!-- reborn-vibe4:guide — 이 표식이 있는 폴더만 설치기가 덮어쓴다 -->
7
+
8
+ # 바이브 코딩 전에 깔 플러그인 넷 — 붙이기 전에 알아야 할 것
9
+
10
+ Supabase · Vercel · Oh My Claude Code · Chrome DevTools MCP 는 **각각 다른 회사·프로젝트**가 만든 것이며
11
+ **리본랩스와 제휴 관계가 없다.** 이 스킬은 그 넷을 깔지도, 대신 실행하지도 않는다 —
12
+ **공식 설치 명령과, 붙이기 전에 알아야 할 것**만 말한다.
13
+
14
+ 사실은 전부 같은 폴더의 `facts.json` 에 있다(확인일 2026-10-01). **거기 없는 건 말하지 않는다.**
15
+
16
+ ---
17
+
18
+ ## 0. 먼저 — 지금 뭐가 붙어 있나 본다
19
+
20
+ ```
21
+ npx --yes reborn-vibe4 --check
22
+ ```
23
+
24
+ 붙은 것은 `✓`, 없는 것은 `✗` 로 나온다. **이미 붙은 걸 또 붙이자고 하지 않는다.**
25
+ (`--check` 는 파일만 읽는다 — 아무것도 깔거나 고치지 않는다.)
26
+
27
+ ---
28
+
29
+ ## 1. 넷이 서로 다른 몫을 한다
30
+
31
+ | | 도구 | 몫 |
32
+ |---|---|---|
33
+ | 1 | **Supabase 플러그인** | DB — SQL · 마이그레이션 · 타입 생성 · 보안 점검 |
34
+ | 2 | **Vercel 플러그인** | 배포 — 빌드 상태 · 로그 · 환경변수 · 도메인 |
35
+ | 3 | **Oh My Claude Code** | 팀 — 역할 나뉜 에이전트 19명을 `/team` 으로 굴린다 |
36
+ | 4 | **Chrome DevTools MCP** | 눈 — 실제 크롬의 콘솔 · 네트워크 · 스크린샷 · 성능 |
37
+
38
+ ---
39
+
40
+ ## 2. 설치 명령 — 사용자가 보고 직접 친다
41
+
42
+ `/` 로 시작하는 줄은 **클로드 코드 안에서** 치는 슬래시 명령이고, `claude mcp add` 는 **터미널**(또는 `!` 뒤)에서 친다.
43
+ OMC 는 두 줄을 **한 번에 붙이면 실패**한다 — 하나씩 입력한다.
44
+
45
+ ### ① Supabase 플러그인 (`supabase-community/supabase-plugin` · MIT)
46
+
47
+ ```
48
+ /plugin install supabase@claude-plugins-official
49
+ ```
50
+
51
+ ### ② Vercel 플러그인 (`vercel/vercel-plugin` · Apache-2.0)
52
+
53
+ ```
54
+ /plugin install vercel@claude-plugins-official
55
+ ```
56
+
57
+ 저장소 README 가 안내하는 다른 길: `npx plugins add vercel/vercel-plugin` (Node 18+ · Bun 필요). 같은 것을 두 길로 까는 것이니 하나만 쓴다.
58
+
59
+ ### ③ Oh My Claude Code (`Yeachan-Heo/oh-my-claudecode` · MIT)
60
+
61
+ ```
62
+ /plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode
63
+ ```
64
+
65
+ ```
66
+ /plugin install oh-my-claudecode
67
+ ```
68
+
69
+ 호출은 `/team 3:executor "fix all TypeScript errors"` 모양이다. 에이전트 19명은 저장소 `agents/` 폴더의 파일을 오늘 직접 센 값이다.
70
+
71
+ ### ④ Chrome DevTools MCP (`ChromeDevTools/chrome-devtools-mcp` · Apache-2.0)
72
+
73
+ ```
74
+ claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
75
+ ```
76
+
77
+ Node.js LTS 와 최신 안정판 크롬이 필요하다. 스킬까지 같이 받는 플러그인판은 `facts.json` 의 `다른길`.
78
+
79
+ 깔고 나면 클로드 코드를 **다시 켜거나** `/reload-plugins` 를 친다.
80
+
81
+ ---
82
+
83
+ ## 3. ★★Supabase 는 쓰기 권한부터 막는다
84
+
85
+ **플러그인이 묶어 온 MCP 설정에는 `read_only` 가 없다** (저장소 `agents/claude/.mcp.json` 을 직접 확인).
86
+ 즉 기본이 **쓰기 가능**이다 — 클로드가 잘못 판단하면 테이블을 지우고 바꿀 수 있다.
87
+
88
+ Supabase 공식 문서가 권하는 방식은 URL 파라미터다:
89
+
90
+ | 파라미터 | 뜻 |
91
+ |---|---|
92
+ | `read_only=true` | 모든 쿼리를 읽기전용 Postgres 사용자로 실행 |
93
+ | `project_ref=<id>` | 한 프로젝트로 한정(계정 단위 도구가 꺼진다) |
94
+ | `features=<그룹>` | 필요한 도구 그룹만 켠다 |
95
+
96
+ 읽기전용으로 따로 붙이는 줄:
97
+
98
+ ```
99
+ claude mcp add --scope project --transport http supabase "https://mcp.supabase.com/mcp?project_ref=<프로젝트ID>&read_only=true"
100
+ ```
101
+
102
+ 공식 문서는 **프로덕션 프로젝트**에는 project_ref 한정 · read_only · features 제한을 권하고, 프롬프트 인젝션 위험을 따로 적는다.
103
+ 쓰기가 꼭 필요하면 **개발용 프로젝트**에서만 연다. 쓰기를 막으려는 사람은 플러그인의 번들 MCP 에 기대지 말고 위 한 줄만 쓰는 쪽이 확실하다.
104
+
105
+ ---
106
+
107
+ ## 4. ★토큰 비용 — 넷을 다 켜지 않는다
108
+
109
+ MCP 서버와 플러그인은 **도구 스키마·스킬 설명이 쓰든 안 쓰든 매 호출 문맥에 실린다.**
110
+ Chrome DevTools MCP 는 도구가 59개다(공식 문서의 도구 제목 수) — 기본 브라우저 작업만 필요하면 문서의 `--slim` 모드가 있다.
111
+ **지금 일에 필요한 것만 켜고, 안 쓰는 것은 `/plugin` 이나 `/mcp` 에서 끈다.**
112
+
113
+ ---
114
+
115
+ ## 5. 그 밖에 알아 둘 것
116
+
117
+ - **Chrome DevTools MCP**: 연결된 크롬의 내용이 모델에 노출된다(README Disclaimers). 모델에 보이면 안 되는 개인·민감 정보는 그 크롬에 띄우지 않는다.
118
+ - **Vercel 플러그인**: 텔레메트리가 기본 켜짐(끄기 `VERCEL_PLUGIN_TELEMETRY=off`), 훅이 같이 설치된다.
119
+ - **OMC**: README 가 CI 같은 무인 환경에서는 `/team` 같은 슬래시 명령에 기대지 말라고 적는다.
120
+
121
+ ---
122
+
123
+ ## 6. 붙었는지 확인하고, 안 되면 지운다
124
+
125
+ ```
126
+ npx --yes reborn-vibe4 --check 이 PC 에 넷이 붙어 있는지 (파일만 읽는다)
127
+ claude mcp list MCP 가 connected 인지
128
+ claude mcp remove <이름> MCP 되돌리기
129
+ /plugin 플러그인 목록·끄기·지우기
130
+ ```
131
+
132
+ **「설치했는데 안 뜬다」** 는 보통 이렇다: ① 클로드 코드를 다시 안 켰다 ② 슬래시 명령을 터미널에 쳤다(`claude mcp add` 는 터미널, `/plugin …` 은 클로드 코드 안) ③ OMC 두 줄을 한 번에 붙였다.
133
+
134
+ ---
135
+
136
+ ## 7. 확인하지 않은 것 (말하지 않는다)
137
+
138
+ - Vercel 의 「deploy my app」 같은 자연어가 어느 스킬로 이어지는지
139
+ - 이 PC 에서 넷을 **실제로 깔아 보지는 않았다** — 명령은 각 공식 문서·README 원문 기준이다
140
+ - Vercel 플러그인의 릴리스 태그(저장소에 릴리스가 없다)
141
+ - 각 서비스의 요금 — 바뀌는 값이라 일부러 안 적었다
142
+
143
+ 모르면 모른다고 말한다. 지어내서 말하지 않는다.
@@ -0,0 +1,68 @@
1
+ {
2
+ "_doc": "퍼널 vibe4 가 말하는 모든 사실의 원장. 스킬·README·설치기는 여기서만 값을 읽는다. 여기에 없는 수치·값은 말하지 않는다. 실측은 GitHub API · 각 저장소 README/LICENSE · Anthropic 공식 마켓플레이스 marketplace.json · supabase.com 공식 문서로 2026-10-01 에 했다.",
3
+ "확인일": "2026-10-01",
4
+ "공식마켓": "claude-plugins-official",
5
+ "도구": [
6
+ {
7
+ "번호": 1,
8
+ "이름": "Supabase 플러그인",
9
+ "저장소": "supabase-community/supabase-plugin",
10
+ "주소": "https://github.com/supabase-community/supabase-plugin",
11
+ "라이선스": "MIT",
12
+ "별": 28,
13
+ "플러그인": "supabase",
14
+ "명령": ["/plugin install supabase@claude-plugins-official"],
15
+ "하는일": "SQL·마이그레이션·타입 생성·보안 점검 같은 슈파베이스 작업을 클로드 코드가 직접 한다(스킬 + 공식 MCP).",
16
+ "읽기전용": "claude mcp add --scope project --transport http supabase \"https://mcp.supabase.com/mcp?project_ref=<프로젝트ID>&read_only=true\"",
17
+ "읽기전용근거": "supabase.com/docs/guides/getting-started/mcp 의 URL 파라미터 표 — read_only=true 는 모든 쿼리를 읽기전용 Postgres 사용자로 실행, project_ref 는 한 프로젝트로 한정",
18
+ "주의": "플러그인이 묶어 온 MCP 설정(agents/claude/.mcp.json)에는 read_only 가 없다 — 기본이 쓰기 가능이다."
19
+ },
20
+ {
21
+ "번호": 2,
22
+ "이름": "Vercel 플러그인",
23
+ "저장소": "vercel/vercel-plugin",
24
+ "주소": "https://github.com/vercel/vercel-plugin",
25
+ "라이선스": "Apache-2.0",
26
+ "별": 295,
27
+ "플러그인": "vercel",
28
+ "명령": ["/plugin install vercel@claude-plugins-official"],
29
+ "다른길": "npx plugins add vercel/vercel-plugin",
30
+ "하는일": "배포·빌드 상태·로그·환경변수·도메인 같은 Vercel 작업을 클로드 코드 안에서 한다. 저장소에 deploy 명령 파일이 있다.",
31
+ "주의": "텔레메트리가 기본 켜져 있다(끄기 VERCEL_PLUGIN_TELEMETRY=off). 훅이 함께 설치된다."
32
+ },
33
+ {
34
+ "번호": 3,
35
+ "이름": "Oh My Claude Code",
36
+ "저장소": "Yeachan-Heo/oh-my-claudecode",
37
+ "주소": "https://github.com/Yeachan-Heo/oh-my-claudecode",
38
+ "라이선스": "MIT",
39
+ "별": 39505,
40
+ "플러그인": "oh-my-claudecode",
41
+ "명령": ["/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode", "/plugin install oh-my-claudecode"],
42
+ "명령메모": "README 가 두 줄을 한 번에 붙이면 실패하니 하나씩 입력하라고 적는다.",
43
+ "하는일": "역할이 나뉜 에이전트 19명(저장소 agents/ 폴더 .md 19개를 센 값)을 팀으로 굴린다. 호출은 /team.",
44
+ "호출": "/team 3:executor \"fix all TypeScript errors\"",
45
+ "주의": "README: 무인 환경(CI)에서는 /team 같은 슬래시 명령에 기대지 말 것."
46
+ },
47
+ {
48
+ "번호": 4,
49
+ "이름": "Chrome DevTools MCP",
50
+ "저장소": "ChromeDevTools/chrome-devtools-mcp",
51
+ "주소": "https://github.com/ChromeDevTools/chrome-devtools-mcp",
52
+ "라이선스": "Apache-2.0",
53
+ "별": 52833,
54
+ "플러그인": "chrome-devtools-mcp",
55
+ "명령": ["claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest"],
56
+ "다른길": "/plugin marketplace add ChromeDevTools/chrome-devtools-mcp 다음 /plugin install chrome-devtools-mcp@chrome-devtools-plugins",
57
+ "하는일": "클로드 코드가 실제 크롬을 열어 콘솔·네트워크·스크린샷·성능 추적을 직접 본다.",
58
+ "주의": "크롬 인스턴스의 내용이 모델에 노출된다(README Disclaimers). 도구가 59개라 스키마가 문맥에 실린다 — 기본 작업만이면 --slim 모드가 있다."
59
+ }
60
+ ],
61
+ "토큰": "MCP 서버와 플러그인은 도구 스키마·스킬 설명이 쓰든 안 쓰든 매 호출 문맥에 실린다. 넷을 다 켜기보다 지금 일에 필요한 것만 켠다.",
62
+ "귀속": { "말": "넷은 각각 다른 회사·프로젝트가 만든 것이며 리본랩스와 제휴 관계가 없습니다." },
63
+ "확인_못한것": [
64
+ "Vercel 의 「deploy my app」 같은 자연어가 어느 스킬로 이어지는지",
65
+ "이 PC 에서 넷을 실제로 깔아 본 것은 아니다 — 명령은 각 공식 문서·README 원문 기준",
66
+ "Vercel 플러그인의 릴리스 태그(저장소에 릴리스가 없다)"
67
+ ]
68
+ }
package/tail.mjs ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 「끝난 뒤 한 칸」 문안 — 정본은 `skillpack/tail_copy.mjs` 다.
3
+ * 사본이 여기 있는 이유는 하나뿐이다: npm 패키지는 폴더 밖을 import 할 수 없다.
4
+ * 고칠 일이 생기면 정본을 먼저 고치고 여기로 옮긴다. 반대로 하지 마라.
5
+ *
6
+ * 규율: ① 끝난 뒤 딱 한 칸 · ② 못 깔았으면 안 띄운다 · ③ 파일을 안 남긴다
7
+ * ④ 고지 블록 금지 · ⑤ 첫 줄은 방금 한 일에서 이어져야 한다
8
+ * ★무료 설치기는 스킬팩 한 칸만 말한다(2026-08-24 전략). 그 다음 칸은 구매 이후다.
9
+ */
10
+ /* ★맨 주소를 상수로 내놓지 않는다 — 조립해서만 내보낸다(utm 이 빠진 채 나가지 않게). */
11
+ const 랜딩경로 = "/skillpack";
12
+ export const 링크 = (퍼널) => `https://rebornlabs.kr${랜딩경로}?utm_source=installer&utm_medium=cli&utm_campaign=${퍼널}`;
13
+
14
+ export const TAIL = {
15
+ vibe4: {
16
+ 이음: [
17
+ "방금 한 일은 **바이브 코딩 전에 깔 플러그인 넷의 공식 설치 명령과 주의점을 한국어로 클로드 코드에 넣은 것**입니다 — Supabase 는 쓰기 권한부터 막는 법까지.",
18
+ "넷은 각각 다른 회사의 오픈소스라 대신 깔지 않았고 우리는 제휴 관계가 없습니다. 명령은 공식 문서 그대로 위에 적어 두었습니다.",
19
+ ],
20
+ 제목: "리본랩스 스킬팩",
21
+ 한줄: "같은 방식으로 골라 담은 48종을 한 줄로. 8,900원",
22
+ },
23
+ };
24
+
25
+ export const 마지막 = "목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.";
@@ -0,0 +1,48 @@
1
+ # 바이브 코딩 플러그인 넷 — 안내 스킬 사용법
2
+
3
+ 클로드 코드로 바이브 코딩을 시작하기 전에 깔 만한 **Supabase 플러그인 · Vercel 플러그인 · Oh My Claude Code · Chrome DevTools MCP** 의
4
+ 공식 설치 명령과, 붙이기 전에 알아야 할 것을 먼저 말해 주는 안내 스킬입니다.
5
+
6
+ ```
7
+ npx --yes reborn-vibe4 # 안내 스킬을 깐다
8
+ npx --yes reborn-vibe4 --check # 이 PC 에 넷이 붙어 있는지 본다 (파일만 읽는다)
9
+ ```
10
+
11
+ 깔리는 자리 `~/.claude/skills/vibe-coding-plugins` · 사실 원장 `skill/facts.json`(확인일 2026-10-01).
12
+
13
+ ★**넷은 깔지도, 대신 실행하지도 않습니다.** 공식 명령을 보여 드리고 사용자가 보고 직접 칩니다.
14
+
15
+ ---
16
+
17
+ ## 먼저 아셔야 할 셋
18
+
19
+ 1. **Supabase 플러그인이 묶어 온 MCP 에는 `read_only` 가 없습니다.** 기본이 쓰기 가능입니다.
20
+ 읽기전용으로 붙이는 줄(공식 문서의 URL 파라미터)을 스킬이 알려 줍니다.
21
+ 2. **플러그인과 MCP 는 안 써도 매 호출 문맥에 실립니다.** 넷을 다 켜지 말고 지금 일에 필요한 것만 켭니다.
22
+ 3. **Chrome DevTools MCP 는 연결된 크롬의 내용을 모델에 노출합니다.** 민감한 탭을 띄우지 않습니다.
23
+
24
+ ## 실측한 값 (2026-10-01)
25
+
26
+ | 도구 | 저장소 | 라이선스 | 설치 |
27
+ |---|---|---|---|
28
+ | Supabase 플러그인 | supabase-community/supabase-plugin | MIT | `/plugin install supabase@claude-plugins-official` |
29
+ | Vercel 플러그인 | vercel/vercel-plugin | Apache-2.0 | `/plugin install vercel@claude-plugins-official` |
30
+ | Oh My Claude Code | Yeachan-Heo/oh-my-claudecode | MIT | `/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode` → `/plugin install oh-my-claudecode` |
31
+ | Chrome DevTools MCP | ChromeDevTools/chrome-devtools-mcp | Apache-2.0 | `claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest` |
32
+
33
+ GitHub API · 각 저장소 README/LICENSE · Anthropic 공식 마켓플레이스 목록 · supabase.com 공식 문서로 직접 확인했습니다.
34
+ 이 PC 에서 넷을 실제로 깔아 본 것은 아닙니다.
35
+
36
+ 넷은 각각 다른 회사·프로젝트가 만든 것이며 **리본랩스와 제휴 관계가 없습니다.**
37
+ 이 안내 스킬과 설치기는 무료이며, 각 도구의 요금은 적지 않았습니다(바뀌는 값).
38
+
39
+ ---
40
+
41
+ ## 깔고 나서 이렇게 말해 보세요
42
+
43
+ ```
44
+ 바이브 코딩 전에 뭘 깔아야 해?
45
+ supabase 플러그인 붙이는데 DB 를 지울까 봐 무서워
46
+ 이 PC 에 넷 중 뭐가 붙어 있어?
47
+ OMC 설치 줄이 안 돼
48
+ ```