reborn-mem3 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,46 @@
1
+ # reborn-mem3
2
+
3
+ 클로드 코드에 붙이는 **무료 스킬 셋**을 한 번에 깔아 주고, 언제 무엇을 부르는지 안내 스킬을 심습니다.
4
+
5
+ ```
6
+ npx --yes reborn-mem3 # 스킬 둘을 실제로 깔고 안내 스킬을 심습니다
7
+ npx --yes reborn-mem3 --check # 깔지 않고 이 컴퓨터를 실제로 잽니다
8
+ npx --yes reborn-mem3 --with-mem # claude-mem 까지 함께 깝니다
9
+ ```
10
+
11
+ ## 무엇이 깔리나
12
+
13
+ | | 무엇 | 어디 |
14
+ |---|---|---|
15
+ | **claude-mem** | 세션이 바뀌어도 프로젝트를 기억한다 | github.com/thedotmack/claude-mem · Apache-2.0 |
16
+ | **find-skills** | 뭘 만드는지 말하면 맞는 스킬을 찾아 깔아 준다 | github.com/vercel-labs/skills · Apache-2.0 |
17
+ | **VibeSec** | 클로드가 쓴 코드에서 흔한 보안 구멍을 짚는다 | github.com/BehiSecc/VibeSec-Skill · Apache-2.0 |
18
+
19
+ **셋 다 저희가 만든 것이 아닙니다.** 남의 오픈소스이고, 이 패키지는 그 코드를 담지도
20
+ 재배포하지도 않습니다 — 공개된 설치 명령을 대신 돌리고 안내만 심습니다.
21
+ 직접 하셔도 됩니다. 명령은 위 저장소에 그대로 적혀 있습니다.
22
+
23
+ ## claude-mem 은 기본으로 안 깝니다
24
+
25
+ `~/.claude/plugins` 와 `~/.claude-mem` 을 만들고 워커를 띄웁니다.
26
+ 남의 컴퓨터에 그걸 묻지도 않고 하지 않습니다. `--with-mem` 을 줄 때만 돌립니다.
27
+
28
+ ## 조용히 막히는 자리 셋 (2026-09-06 실측)
29
+
30
+ 1. **에이전트 이름은 `claude` 가 아니라 `claude-code`** 입니다. `claude` 로 주면
31
+ 「Invalid agents」 로 끝나는데, 사람이 안 보면 깔린 줄 압니다.
32
+ 2. **claude-mem 은 Bun 이 있어야 합니다.** 없으면 플러그인 등록까지는 되고
33
+ 워커 단계에서 `bun-missing-after-install` 로 섭니다.
34
+ 3. **VibeSec 저장소 이름의 c 가 두 개**(`BehiSecc`)입니다. 하나면 404 입니다.
35
+
36
+ ## --check 가 재는 것
37
+
38
+ Node 18+ · npx · git · Bun(claude-mem 전용) · `~/.claude/skills` 에 쓸 수 있나.
39
+ 못 재면 「못 쟀다」고 합니다. 「있다」고 하지 않습니다.
40
+
41
+ ## 되돌리기
42
+
43
+ - 스킬: `~/.claude/skills/` 에서 `find-skills` · `vibesec-skill` · `mem3-guide` 폴더를 지웁니다.
44
+ - claude-mem: 클로드 코드에서 `/plugin` 으로 끕니다.
45
+
46
+ MIT © REBORN LABS
package/cli.mjs ADDED
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 클로드 코드 무료 스킬 셋 설치기 — **실제로 깔아 주고**, 언제 무엇을 부르는지 안내 스킬을 심는다.
4
+ *
5
+ * npx --yes reborn-mem3 스킬 둘을 실제로 깔고 안내 스킬을 심는다
6
+ * npx --yes reborn-mem3 --with-mem claude-mem 까지 함께 깐다 (플러그인·홈을 만진다)
7
+ * npx --yes reborn-mem3 --check ★깔지 않고 **이 컴퓨터를 실제로 잰다**
8
+ * npx --yes reborn-mem3 --dry-run 무엇을 할지 보여주기만 한다
9
+ * npx --yes reborn-mem3 --local 홈이 아니라 현재 폴더에 깐다
10
+ * npx --yes reborn-mem3 --self-test 자기시험 (네트워크 안 씀)
11
+ *
12
+ * ★★셋 다 우리가 만든 것이 아니다.
13
+ * claude-mem(thedotmack) · find-skills(vercel-labs) · VibeSec(BehiSecc) 전부 남의 오픈소스다.
14
+ * 이 패키지는 그 **코드를 담지 않는다.** 공개된 설치 명령을 대신 돌리고, 안내 스킬만 심는다.
15
+ *
16
+ * ★★claude-mem 은 기본으로 안 깐다
17
+ * `~/.claude/plugins` 와 `~/.claude-mem` 을 만들고 워커를 띄운다. 남의 컴퓨터에 그걸
18
+ * 묻지도 않고 하지 않는다. `--with-mem` 을 줄 때만 돌린다. 그게 이 설치기의 규율이다.
19
+ *
20
+ * ★--check 가 이 설치기의 절반이다
21
+ * 「명령 한 줄」로 읽히는데 실제로는 세 군데서 막힌다(에이전트 이름 · Bun · 저장소 오타).
22
+ * 말로 「준비물이 있습니다」 하지 않고 **지금 이 컴퓨터에서 다섯 개를 직접 잰다.**
23
+ * 못 재면 「못 쟀다」고 한다 — 「있다」고 하지 않는다.
24
+ */
25
+ import { existsSync, mkdirSync, readFileSync, cpSync, rmSync, writeFileSync } from "node:fs";
26
+ import { homedir, platform, tmpdir } from "node:os";
27
+ import { dirname, join } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import { execFileSync, execSync } from "node:child_process";
30
+ import { TAIL, 링크, 마지막 } from "./tail.mjs";
31
+
32
+ const HOME = homedir();
33
+ const 여기 = dirname(fileURLToPath(import.meta.url));
34
+ const 인자 = process.argv.slice(2);
35
+ const 확인만 = 인자.includes("--check");
36
+ const 미리보기 = 인자.includes("--dry-run");
37
+ const 여기에 = 인자.includes("--local");
38
+ const 자기시험모드 = 인자.includes("--self-test");
39
+ const 맴까지 = 인자.includes("--with-mem");
40
+
41
+ const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
42
+ const 굵게 = (t) => String(t).replace(/\*\*(.+?)\*\*/g, `${c[0]}${c.b}$1${c[0]}${c.d}`);
43
+ const 콤마 = (n) => Number(n).toLocaleString("ko-KR");
44
+
45
+ const 퍼널 = "mem3";
46
+ const 스킬이름 = "mem3-guide";
47
+ const 원본 = join(여기, "skill");
48
+ const 대상뿌리 = 여기에 ? join(process.cwd(), ".claude", "skills") : join(HOME, ".claude", "skills");
49
+ const 대상 = join(대상뿌리, 스킬이름);
50
+
51
+ /* ══ 원장 ═══════════════════════════════════════════════════════ */
52
+
53
+ function 원장읽기() {
54
+ const p = join(원본, "mem3.json");
55
+ if (!existsSync(p)) return { 됨: false, 이유: `원장이 없다 — ${p}` };
56
+ try { return { 됨: true, d: JSON.parse(readFileSync(p, "utf8")) }; }
57
+ catch (e) { return { 됨: false, 이유: `원장을 못 읽었다 — ${e.message}` }; }
58
+ }
59
+
60
+ /** ★조건 없이 단정하는 말 [[kmong-no-unbounded-guarantee]] */
61
+ const 금칙 = [/완전\s*무료/, /무제한/, /평생/, /100\s*%\s*보장/, /누구나\s*바로/, /무조건/,
62
+ /취약점이\s*없어/, /완벽/, /전액\s*환불/];
63
+ const 금칙면제 = /(하지\s*않는다|말하지\s*마라|쓰지\s*않는다|아니다|틀리다|금칙|안\s*된다|하지\s*마라|말하지\s*않는다|은\s*아니다|되지\s*않는다|마라|않는다|전부가\s*아니다)/;
64
+ /** ★검사기가 자기 인용을 막지 않게 낫표 안은 벗기고 잰다 [[forbidden-word-checker-blocks-its-own-quotes]] */
65
+ const 인용벗기기 = (s) => String(s).replace(/「[^」]*」/g, "○");
66
+
67
+ function 금칙검사(텍스트, 어디) {
68
+ const 문제 = [];
69
+ for (const 줄 of String(텍스트).split("\n")) {
70
+ const 벗긴 = 인용벗기기(줄);
71
+ if (금칙면제.test(벗긴)) continue;
72
+ for (const re of 금칙) if (re.test(벗긴)) 문제.push(`${어디}: 조건 없이 단정하는 말 — ${줄.trim().slice(0, 60)}`);
73
+ }
74
+ return 문제;
75
+ }
76
+
77
+ function 원장검사(d) {
78
+ const 문제 = [];
79
+ const 스 = Array.isArray(d?.스킬) ? d.스킬 : [];
80
+ if (스.length !== 3) 문제.push(`원장 스킬이 ${스.length}개다 — 이 설치기는 셋을 깐다`);
81
+ for (const s of 스) {
82
+ if (!/^https:\/\/github\.com\//.test(String(s.주소 || ""))) 문제.push(`${s.키}: 저장소 주소가 GitHub 이 아니다`);
83
+ if (s.라이선스 !== "Apache-2.0") 문제.push(`${s.키}: 라이선스 칸이 Apache-2.0 이 아니다 — 확인 없이 안내하지 않는다`);
84
+ if (!s.설치) 문제.push(`${s.키}: 설치 명령이 없다`);
85
+ if (!Number.isFinite(s.별점)) 문제.push(`${s.키}: 별점이 수치가 아니다 — 화면에 그대로 뜬다`);
86
+ }
87
+ if (!String(d?.묶음?.["★면책"] || "").includes("우리가 만든 것이 아니다"))
88
+ 문제.push("★면책이 사라졌다 — 남의 저장소를 크게 세우면 귀속을 반드시 박는다");
89
+ const 관문 = Array.isArray(d?.관문) ? d.관문 : [];
90
+ if (관문.length < 5) 문제.push(`관문이 ${관문.length}개다 — 다섯을 잰다`);
91
+ for (const g of 관문) {
92
+ if (!g.재는법) 문제.push(`관문 ${g.키} 에 재는법이 없다 — 못 재면 있다고 말하게 된다`);
93
+ if (!g.없으면) 문제.push(`관문 ${g.키} 에 「없으면」이 없다 — 막힌 사람을 그냥 두는 것이다`);
94
+ if (!g.출처) 문제.push(`관문 ${g.키} 에 출처가 없다 — 지어낸 준비물과 구분이 안 된다`);
95
+ }
96
+ if (!Array.isArray(d?.["★막히는자리"]) || d["★막히는자리"].length < 3)
97
+ 문제.push("★막히는자리가 셋보다 적다 — 이 설치기가 파는 것이 그것이다");
98
+ for (const m of d?.["★막히는자리"] || []) if (!m.근거) 문제.push(`막히는자리 「${m.제목}」 에 근거가 없다`);
99
+ 문제.push(...금칙검사(JSON.stringify(d, null, 1), "원장"));
100
+ return 문제;
101
+ }
102
+
103
+ /* ══ 이 컴퓨터를 잰다 ═══════════════════════════════════════════ */
104
+
105
+ function 판재기(cmd, 인자들) {
106
+ try {
107
+ /* ★윈도우의 npx·bun 은 .cmd 라 셸이 필요하다. 그런데 shell:true 에 배열을 같이 주면
108
+ 인자가 이스케이프 없이 이어붙어 DEP0190 이 뜨고, 실제로 지시문이 뭉개진다
109
+ [[claude-cli-shell-true-mangles-args]]. 그래서 **문자열 하나로** 넘긴다.
110
+ 여기 들어가는 명령은 전부 우리 원장에서 온 고정 문자열이다 — 사용자 입력이 아니다. */
111
+ const 줄 = [cmd, ...인자들].join(" ");
112
+ const out = execSync(줄, { stdio: "pipe", timeout: 15000, encoding: "utf8" });
113
+ const 첫줄 = String(out).split("\n").find((l) => l.trim()) || "";
114
+ return 첫줄.trim().slice(0, 80);
115
+ } catch { return null; }
116
+ }
117
+
118
+ function 판수(s) {
119
+ const m = String(s || "").match(/(\d+)\.(\d+)(?:\.(\d+))?/);
120
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3] || 0)] : null;
121
+ }
122
+
123
+ function 하한충족(판, 하한) {
124
+ const a = 판수(판), b = 판수(하한);
125
+ if (!a || !b) return null;
126
+ for (let i = 0; i < 3; i++) { if (a[i] !== b[i]) return a[i] > b[i]; }
127
+ return true;
128
+ }
129
+
130
+ /** 폴더에 실제로 쓸 수 있나 — 「있다」고 짐작하지 않고 만들어 본다. */
131
+ function 쓸수있나(뿌리) {
132
+ try {
133
+ mkdirSync(뿌리, { recursive: true });
134
+ const p = join(뿌리, `.mem3_probe_${process.pid}`);
135
+ writeFileSync(p, "x");
136
+ rmSync(p, { force: true });
137
+ return true;
138
+ } catch { return false; }
139
+ }
140
+
141
+ function 관문재기(D) {
142
+ return D.관문.map((g) => {
143
+ if (g.키 === "skills디렉터리") {
144
+ const ok = 쓸수있나(대상뿌리);
145
+ return { ...g, 있나: ok, 판: ok ? 대상뿌리 : null, 충족: ok };
146
+ }
147
+ const 판 = 판재기(g.재는법.split(" ")[0], g.재는법.split(" ").slice(1));
148
+ const 있나 = 판 !== null;
149
+ const 충족 = 있나 && g.하한 ? 하한충족(판, g.하한) : 있나;
150
+ return { ...g, 있나, 판, 충족 };
151
+ });
152
+ }
153
+
154
+ function 환경보고(D, 잰것) {
155
+ console.log(`\n${c.b}클로드 코드 무료 스킬 셋${c[0]} ${c.d}— ${D.묶음.무엇}${c[0]}`);
156
+ console.log(`${c.d}${"─".repeat(58)}${c[0]}`);
157
+ for (const s of D.스킬) {
158
+ const 뒤 = s.설치수 ? `설치 ${s.설치수}` : `별 ${콤마(s.별점)}`;
159
+ console.log(` ${c.b}${s.이름.padEnd(12)}${c[0]} ${s.한줄}`);
160
+ console.log(` ${c.d}${" ".repeat(13)}${s.주소.replace("https://github.com/", "")} · ${s.라이선스} · ${뒤}${c[0]}`);
161
+ }
162
+ console.log(`${c.d}${"─".repeat(58)}${c[0]}`);
163
+ console.log(`${c.d}이 컴퓨터를 재 봤습니다 — 다섯 가지${c[0]}`);
164
+ for (const x of 잰것) {
165
+ const 표 = x.충족 === true ? `${c.g}✓${c[0]}` : x.있나 ? `${c.y}?${c[0]}` : `${c.r}✗${c[0]}`;
166
+ const 값 = x.판 ? `${c.d}${x.판}${c[0]}` : `${c.d}못 쟀습니다${c[0]}`;
167
+ console.log(` ${표} ${x.이름.padEnd(24)} ${값}`);
168
+ if (x.충족 !== true) console.log(` ${c.d}${굵게(x.없으면)}${c[0]}`);
169
+ }
170
+ console.log("");
171
+ for (const m of D["★막히는자리"]) {
172
+ console.log(` ${c.y}·${c[0]} ${c.b}${m.제목}${c[0]}`);
173
+ console.log(` ${c.d}${m.내용}${c[0]}`);
174
+ }
175
+ console.log("");
176
+ }
177
+
178
+ /* ══ 실제로 깐다 ════════════════════════════════════════════════ */
179
+
180
+ function 스킬깔기(s) {
181
+ /* --local 이면 프로젝트 폴더에서 돌린다 — skills 는 cwd 기준으로 심는다 */
182
+ /* ★같은 이유로 문자열 하나다. s.설치 는 원장의 고정 명령이다 */
183
+ execSync(s.설치, {
184
+ stdio: "inherit", timeout: 8 * 60e3,
185
+ cwd: 여기에 ? process.cwd() : HOME,
186
+ });
187
+ }
188
+
189
+ function 안내스킬심기() {
190
+ mkdirSync(대상, { recursive: true });
191
+ for (const f of ["SKILL.md", "mem3.json"]) cpSync(join(원본, f), join(대상, f));
192
+ return 대상;
193
+ }
194
+
195
+ /* ══ 끝난 뒤 한 칸 ══════════════════════════════════════════════ */
196
+
197
+ function 꼬리(깔았나) {
198
+ if (!깔았나) return; /* ② 아무것도 못 깔았으면 안 띄운다 */
199
+ const t = TAIL[퍼널];
200
+ if (!t) return;
201
+ console.log(`\n${c.d}${"─".repeat(58)}${c[0]}`);
202
+ for (const l of t.이음) console.log(`${c.d}${굵게(l)}${c[0]}`);
203
+ console.log("");
204
+ console.log(`${c.b}${t.제목}${c[0]}${c.d} — ${t.한줄}${c[0]}`);
205
+ console.log(`${c.cy}${링크(퍼널)}${c[0]}`);
206
+ console.log(`${c.d}${마지막}${c[0]}\n`);
207
+ }
208
+
209
+ /* ══ 자기시험 — 네트워크를 안 쓴다 ══════════════════════════════ */
210
+
211
+ function 자기시험() {
212
+ const 나쁨 = [];
213
+ const r = 원장읽기();
214
+ if (!r.됨) { console.log(`${c.r}✗ ${r.이유}${c[0]}`); process.exit(1); }
215
+ const D = r.d;
216
+
217
+ const 문제 = 원장검사(D);
218
+ if (문제.length) 나쁨.push(...문제);
219
+
220
+ /* ★검사기를 **양방향으로** 시험한다 — 전건 통과는 고장 신호다
221
+ [[verifier-must-be-tested-both-ways]] */
222
+ {
223
+ const x = JSON.parse(JSON.stringify(D));
224
+ x.묶음["★면책"] = "";
225
+ if (원장검사(x).length <= 문제.length) 나쁨.push("검사기가 ★면책 삭제를 못 잡는다");
226
+ }
227
+ {
228
+ const x = JSON.parse(JSON.stringify(D));
229
+ x.스킬[0].라이선스 = "MIT";
230
+ if (원장검사(x).length <= 문제.length) 나쁨.push("검사기가 라이선스 변조를 못 잡는다");
231
+ }
232
+ {
233
+ const x = JSON.parse(JSON.stringify(D));
234
+ x.묶음.무엇 = "무제한으로 다 됩니다";
235
+ if (원장검사(x).length <= 문제.length) 나쁨.push("검사기가 금칙어를 못 잡는다");
236
+ }
237
+ {
238
+ /* 오탐 시험 — 부정문 안의 금칙어는 잡히면 안 된다 */
239
+ const x = JSON.parse(JSON.stringify(D));
240
+ x.묶음.무엇 = "무제한이라고 말하지 않는다";
241
+ if (원장검사(x).length > 문제.length) 나쁨.push("오탐: 금칙어를 부정문으로 적었는데 잡힌다");
242
+ }
243
+
244
+ const 잰것 = 관문재기(D);
245
+ if (잰것.length !== D.관문.length) 나쁨.push(`관문재기가 ${잰것.length}줄을 냈다 — ${D.관문.length}줄이어야 한다`);
246
+
247
+ for (const f of ["SKILL.md", "mem3.json"]) {
248
+ if (!existsSync(join(원본, f))) 나쁨.push(`심을 원본에 ${f} 이 없다`);
249
+ }
250
+
251
+ /* ★설치 명령의 에이전트 이름 — 우리가 실제로 밟은 함정이다 */
252
+ for (const s of D.스킬) {
253
+ if (s.설치방식 === "스킬" && !/-a claude-code\b/.test(s.설치))
254
+ 나쁨.push(`${s.키}: 설치 명령의 에이전트가 claude-code 가 아니다 — Invalid agents 로 조용히 안 깔린다`);
255
+ }
256
+ if (!/BehiSecc/.test(D.스킬.find((s) => s.키 === "vibesec")?.주소 || ""))
257
+ 나쁨.push("VibeSec 주소의 c 가 하나다 — 404 가 난다");
258
+
259
+ if (나쁨.length) {
260
+ console.log(`\n${c.r}✗ 자기시험 ${나쁨.length}건${c[0]}`);
261
+ for (const m of 나쁨) console.log(` ${c.r}· ${m}${c[0]}`);
262
+ process.exit(1);
263
+ }
264
+ console.log(`\n${c.g}✓ 자기시험 통과${c[0]} ${c.d}— 원장 검사 양방향 · 관문 ${잰것.length}줄 실측 · 설치 명령 규격 · 심을 원본 확인${c[0]}\n`);
265
+ }
266
+
267
+ /* ══ 본체 ═══════════════════════════════════════════════════════ */
268
+
269
+ async function 본체() {
270
+ if (자기시험모드) return 자기시험();
271
+
272
+ const r = 원장읽기();
273
+ if (!r.됨) { console.log(`${c.r}✗ ${r.이유}${c[0]}`); process.exit(1); }
274
+ const D = r.d;
275
+
276
+ const 문제 = 원장검사(D);
277
+ if (문제.length) {
278
+ console.log(`${c.r}✗ 원장이 규격을 벗어났다 — 안내를 내보내지 않는다${c[0]}`);
279
+ for (const m of 문제) console.log(` ${c.r}· ${m}${c[0]}`);
280
+ process.exit(1);
281
+ }
282
+
283
+ const 잰것 = 관문재기(D);
284
+ 환경보고(D, 잰것);
285
+
286
+ if (확인만) {
287
+ console.log(`${c.d}재기만 했습니다. 깔려면 ${c[0]}${c.b}npx --yes reborn-mem3${c[0]}${c.d} 를 그대로 실행하세요.${c[0]}\n`);
288
+ return;
289
+ }
290
+
291
+ const 깔대상 = D.스킬.filter((s) => s.설치방식 === "스킬" || 맴까지);
292
+
293
+ if (미리보기) {
294
+ console.log(`${c.d}--dry-run — 실제로는 안 깝니다.${c[0]}`);
295
+ for (const s of 깔대상) console.log(` ${c.b}${s.이름}${c[0]} ${c.d}${s.설치}${c[0]}`);
296
+ if (!맴까지) console.log(` ${c.d}claude-mem 은 건너뜁니다 — 홈과 플러그인을 만집니다. 함께 깔려면 --with-mem${c[0]}`);
297
+ console.log(` 안내 스킬 ${대상}`);
298
+ console.log(` ${c.d}저장소 코드는 담지 않습니다. 공개된 설치 명령을 대신 돌릴 뿐입니다.${c[0]}\n`);
299
+ return;
300
+ }
301
+
302
+ const 막힘 = 잰것.find((x) => x.키 === "skills디렉터리" && !x.충족);
303
+ if (막힘) {
304
+ console.log(`${c.r}✗ ${대상뿌리} 에 쓸 수 없습니다 — --local 로 현재 폴더에 깔 수 있습니다${c[0]}\n`);
305
+ process.exit(1);
306
+ }
307
+
308
+ let 깔았나 = false;
309
+ const 결과 = [];
310
+ for (const s of 깔대상) {
311
+ if (s.키 === "claude-mem") {
312
+ const bun = 잰것.find((x) => x.키 === "bun");
313
+ if (!bun?.있나) {
314
+ 결과.push({ 이름: s.이름, 됨: false, 왜: "Bun 이 없습니다 — winget install Oven-sh.Bun 후 다시" });
315
+ continue;
316
+ }
317
+ }
318
+ try {
319
+ console.log(`${c.d}$ ${s.설치}${c[0]}`);
320
+ 스킬깔기(s);
321
+ 결과.push({ 이름: s.이름, 됨: true });
322
+ 깔았나 = true;
323
+ } catch (e) {
324
+ 결과.push({ 이름: s.이름, 됨: false, 왜: String(e.message).slice(0, 90) });
325
+ }
326
+ }
327
+
328
+ try {
329
+ const 자리 = 안내스킬심기();
330
+ 깔았나 = true;
331
+ 결과.push({ 이름: "mem3-guide (안내)", 됨: true, 어디: 자리 });
332
+ } catch (e) {
333
+ 결과.push({ 이름: "mem3-guide (안내)", 됨: false, 왜: String(e.message).slice(0, 90) });
334
+ }
335
+
336
+ console.log(`\n${c.d}${"─".repeat(58)}${c[0]}`);
337
+ for (const x of 결과) {
338
+ console.log(x.됨
339
+ ? ` ${c.g}✓${c[0]} ${x.이름}${x.어디 ? ` ${c.d}${x.어디}${c[0]}` : ""}`
340
+ : ` ${c.r}✗${c[0]} ${x.이름} ${c.d}— ${x.왜}${c[0]}`);
341
+ }
342
+ if (!맴까지) {
343
+ console.log(`\n${c.d}claude-mem 은 안 깔았습니다 — 홈과 플러그인을 만지기 때문입니다.${c[0]}`);
344
+ console.log(`${c.d}함께 깔려면 ${c[0]}${c.b}npx --yes reborn-mem3 --with-mem${c[0]}${c.d}, 직접 하시려면 ${c[0]}${c.b}npx claude-mem install${c[0]}`);
345
+ }
346
+ console.log(`${c.d}클로드 코드를 다시 켜면 ${c[0]}${c.b}${스킬이름}${c[0]}${c.d} 로 잡힙니다.${c[0]}`);
347
+
348
+ if (결과.some((x) => !x.됨)) process.exitCode = 1;
349
+ 꼬리(깔았나);
350
+ }
351
+
352
+ 본체().catch((e) => { console.log(`${c.r}✗ ${e?.message || e}${c[0]}`); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "reborn-mem3",
3
+ "version": "1.0.0",
4
+ "description": "클로드 코드에 붙이는 무료 스킬 셋(claude-mem · find-skills · VibeSec)을 한 번에 깔아 주고, 언제 무엇을 부르는지 안내 스킬을 심습니다. --check 가 이 컴퓨터에서 다섯 가지(Node 18+ · npx · git · Bun · ~/.claude/skills 쓰기)를 실제로 재 줍니다. 셋 다 남의 오픈소스이고 저장소 코드는 담지 않습니다 — 공개된 설치 명령을 대신 돌릴 뿐입니다.",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "agent-skills",
9
+ "claude-mem",
10
+ "find-skills",
11
+ "vibesec",
12
+ "memory",
13
+ "security",
14
+ "skills",
15
+ "korean"
16
+ ],
17
+ "homepage": "https://rebornlabs.kr/mem3?utm_source=installer&utm_medium=cli&utm_campaign=mem3",
18
+ "bugs": {
19
+ "url": "https://mobility.rebornlabs.kr/cs"
20
+ },
21
+ "license": "MIT",
22
+ "author": "REBORN LABS",
23
+ "type": "module",
24
+ "bin": {
25
+ "reborn-mem3": "cli.mjs"
26
+ },
27
+ "files": [
28
+ "cli.mjs",
29
+ "tail.mjs",
30
+ "skill/",
31
+ "README.md"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: mem3-guide
3
+ description: 클로드 코드에 붙이는 무료 스킬 셋(claude-mem · find-skills · VibeSec)을 언제 어느 것으로 부르는지, 그리고 깔 때 무엇이 조용히 막는지 알려 준다. "claude-mem", "클로드 맴", "세션 기억", "컨텍스트가 매번 날아간다", "find-skills", "스킬 찾아줘", "어떤 스킬 깔지 모르겠다", "skills.sh", "VibeSec", "바이브섹", "AI가 쓴 코드 보안", "API 키가 프런트에 박혔다", "클로드 코드 무료 스킬" 같은 이야기가 나올 때 쓴다.
4
+ ---
5
+
6
+ # 클로드 코드 무료 스킬 셋 — 언제 무엇을 부르나
7
+
8
+ 셋 다 **남이 공개해 둔 오픈소스**다. 리본랩스가 만든 것이 아니고, 이 안내 스킬도
9
+ 그 코드를 담지 않는다. 여기 적힌 수치는 **2026-09-06 에 실제로 재고 깔아 본 것**이다.
10
+
11
+ ## 먼저 — 가장 자주 틀리는 셋
12
+
13
+ | 틀리는 것 | 맞는 것 |
14
+ |---|---|
15
+ | `npx skills add … -a claude` | **`-a claude-code`** 다. `claude` 로 주면 「Invalid agents」 로 조용히 안 깔린다 |
16
+ | `github.com/BehiSec/VibeSec-Skill` | **`BehiSecc`** — c 가 두 개다. 하나면 404 |
17
+ | 「claude-mem 은 명령 한 줄」 | 맞지만 **Bun 이 있어야 한다.** 없으면 플러그인 등록까지만 되고 워커에서 선다 |
18
+
19
+ ## 셋이 각각 무엇인가
20
+
21
+ ### 1. claude-mem — 세션 사이의 기억
22
+ `github.com/thedotmack/claude-mem` · Apache-2.0 · 별 93,318
23
+
24
+ 세션이 끝날 때까지 있었던 일을 압축해 두고 다음 세션에 다시 넣어 준다.
25
+ 「지난번에 하던 결제 페이지 이어서」가 되는 이유가 이것이다.
26
+
27
+ ```
28
+ npx claude-mem install --provider claude
29
+ ```
30
+
31
+ - **Bun 이 필요하다.** 없으면 설치기가 받으러 가고 거기서 막힐 수 있다
32
+ (`winget install Oven-sh.Bun` 으로 먼저 깔아 두면 깔끔하다).
33
+ - `~/.claude/plugins` 와 `~/.claude-mem` 을 만진다. 되돌리려면 `/plugin` 에서 끈다.
34
+ - 워커가 `127.0.0.1:37777` 에서 돈다. `npx claude-mem status` 로 확인한다.
35
+ - 저장소 이름이 **Grok Mem** 으로 바뀌었다. 패키지 이름은 여전히 `claude-mem` 이다.
36
+
37
+ ### 2. find-skills — 무슨 스킬을 깔지 대신 골라 준다
38
+ `github.com/vercel-labs/skills` · Apache-2.0 · 별 30,497 · 설치 330만(skills.sh All Time 1위)
39
+
40
+ 「프런트엔드 디자인 스킬 찾아 줘」처럼 **만들고 있는 것**을 말하면
41
+ skills.sh 리더보드를 먼저 보고 설치 수·출처로 걸러서 `npx skills add` 명령까지 만들어 준다.
42
+
43
+ ```
44
+ npx skills add https://github.com/vercel-labs/skills --skill find-skills -a claude-code -y --copy
45
+ ```
46
+
47
+ - 깔지 않고 검색만 해 보려면 `npx skills find "<찾는 것>"`.
48
+ - `--copy` 를 빼면 심볼릭 링크로 걸린다. 윈도우에서는 권한 때문에 `--copy` 가 안전하다.
49
+
50
+ ### 3. VibeSec — 클로드가 쓴 코드를 다시 읽는다
51
+ `github.com/BehiSecc/VibeSec-Skill` · Apache-2.0 · 별 1,262
52
+
53
+ 버그 바운티 헌터의 시선으로 코드를 읽어 **흔한 구멍**을 짚는다 —
54
+ 프런트에 박힌 키, 남의 데이터를 그대로 부르는 접근 제어, 검증 없이 들어가는 값.
55
+
56
+ ```
57
+ npx skills add https://github.com/BehiSecc/VibeSec-Skill --skill vibesec-skill -a claude-code -y --copy
58
+ ```
59
+
60
+ - **짚어 주는 것이지 없애 주는 것이 아니다.** 저장소도 「흔한 취약점의 60~70%」라고 적어 둔다.
61
+ - 실제로 물려 보니 9줄짜리 파일에서 4건을 짚었다(하드코딩 키 · 접근 제어 · SSRF · 키 노출).
62
+
63
+ ## 언제 무엇을 부르나
64
+
65
+ | 상황 | 부를 것 |
66
+ |---|---|
67
+ | 새 세션마다 프로젝트 설명을 다시 붙여넣고 있다 | **claude-mem** |
68
+ | 「이런 걸 잘하는 스킬 없나」 싶다 | **find-skills** |
69
+ | 클로드가 짠 코드를 남에게 넘기기 직전이다 | **VibeSec** |
70
+
71
+ ## 안 하는 말
72
+
73
+ - 「보안이 완벽해진다」 — 흔한 구멍을 짚는 것이다.
74
+ - 「비용이 없어진다」 — 다시 설명하는 몫이 주는 것이다.
75
+ - 「우리가 만든 스킬」 — 셋 다 남의 오픈소스다.
@@ -0,0 +1,84 @@
1
+ {
2
+ "_doc": "reborn-mem3 설치기 원장 — 화면에 뜨는 값은 전부 여기서 온다. 손으로 안 적는다.",
3
+ "_실측일": "2026-09-06",
4
+ "_어떻게_쟀나": [
5
+ "별점: api.github.com/repos/<owner>/<repo> 의 stargazers_count",
6
+ "설치수: skills.sh 의 find-skills 페이지 (All Time 1위)",
7
+ "★셋 다 임시 폴더에 실제로 깔아 보고 적었다. claude-mem 은 격리 홈에 설치해 워커까지 띄웠다"
8
+ ],
9
+
10
+ "묶음": {
11
+ "이름": "클로드 코드 무료 스킬 셋",
12
+ "무엇": "기억(claude-mem) · 스킬 찾기(find-skills) · 보안 점검(VibeSec)",
13
+ "★면책": "셋 다 우리가 만든 것이 아니다. 남의 오픈소스이고, 이 설치기는 코드를 담지도 재배포하지도 않는다 — 공개된 설치 명령을 대신 돌려 줄 뿐이다"
14
+ },
15
+
16
+ "스킬": [
17
+ {
18
+ "키": "claude-mem",
19
+ "이름": "claude-mem",
20
+ "한줄": "세션이 바뀌어도 프로젝트를 기억한다",
21
+ "주소": "https://github.com/thedotmack/claude-mem",
22
+ "라이선스": "Apache-2.0",
23
+ "별점": 93318,
24
+ "설치": "npx claude-mem install --provider claude",
25
+ "설치방식": "플러그인",
26
+ "★전제": "Bun 이 있어야 한다. 없으면 설치기가 Bun 을 받으러 가고, 그 단계에서 막힐 수 있다(우리도 한 번 막혔다)",
27
+ "★손대는곳": "~/.claude/plugins · ~/.claude-mem — 그래서 기본으로는 안 깐다. --with-mem 을 줄 때만 돌린다"
28
+ },
29
+ {
30
+ "키": "find-skills",
31
+ "이름": "find-skills",
32
+ "한줄": "뭘 만드는지 말하면 맞는 스킬을 찾아 깔아 준다",
33
+ "주소": "https://github.com/vercel-labs/skills",
34
+ "라이선스": "Apache-2.0",
35
+ "별점": 30497,
36
+ "설치수": "330만",
37
+ "설치수출처": "skills.sh · All Time 1위",
38
+ "설치": "npx skills add https://github.com/vercel-labs/skills --skill find-skills -a claude-code -y --copy",
39
+ "설치방식": "스킬",
40
+ "★함정": "에이전트 이름은 claude 가 아니라 claude-code 다. claude 로 주면 「Invalid agents」 로 조용히 안 깔린다 — 우리가 실제로 밟았다"
41
+ },
42
+ {
43
+ "키": "vibesec",
44
+ "이름": "VibeSec",
45
+ "한줄": "클로드가 쓴 코드에서 흔한 보안 구멍을 짚는다",
46
+ "주소": "https://github.com/BehiSecc/VibeSec-Skill",
47
+ "라이선스": "Apache-2.0",
48
+ "별점": 1262,
49
+ "만든이": "버그 바운티 5년+",
50
+ "설치": "npx skills add https://github.com/BehiSecc/VibeSec-Skill --skill vibesec-skill -a claude-code -y --copy",
51
+ "설치방식": "스킬",
52
+ "★함정": "저장소 이름은 BehiSec 이 아니라 BehiSecc(c 두 개)다. 잘못 적으면 404 다",
53
+ "★한계": "저장소가 스스로 「흔한 취약점의 60~70%를 덮는다」고 적어 둔다. 전부가 아니다"
54
+ }
55
+ ],
56
+
57
+ "값": { "가격": "0원", "라이선스": "셋 다 Apache-2.0" },
58
+
59
+ "관문": [
60
+ { "키": "node", "이름": "Node.js 18+", "재는법": "node --version", "하한": "18.0.0",
61
+ "없으면": "nodejs.org 에서 LTS 를 받는다", "출처": "npx 가 Node 에 딸려 있다" },
62
+ { "키": "npx", "이름": "npx", "재는법": "npx --version",
63
+ "없으면": "Node 를 다시 깐다 — npx 는 Node 에 딸려 온다", "출처": "세 스킬 모두 npx 로 깐다" },
64
+ { "키": "git", "이름": "git", "재는법": "git --version",
65
+ "없으면": "git-scm.com 에서 받는다", "출처": "skills 는 저장소를 받아 온다" },
66
+ { "키": "bun", "이름": "Bun (claude-mem 전용)", "재는법": "bun --version",
67
+ "없으면": "claude-mem 만 못 깐다. 나머지 둘은 상관없다. winget install Oven-sh.Bun",
68
+ "출처": "claude-mem 설치기가 워커를 Bun 으로 돌린다 — 우리가 실제로 여기서 한 번 막혔다" },
69
+ { "키": "skills디렉터리", "이름": "~/.claude/skills 에 쓸 수 있나", "재는법": "폴더를 만들어 본다",
70
+ "없으면": "권한을 확인하거나 --local 로 현재 폴더에 깐다", "출처": "스킬이 그 자리에 들어간다" }
71
+ ],
72
+
73
+ "★막히는자리": [
74
+ { "제목": "에이전트 이름이 claude 가 아니다",
75
+ "내용": "`-a claude` 로 주면 Invalid agents 로 끝난다. claude-code 여야 한다",
76
+ "근거": "2026-09-06 실측 — 첫 시도가 그렇게 실패했다" },
77
+ { "제목": "claude-mem 은 Bun 이 없으면 중간에 선다",
78
+ "내용": "플러그인 등록까지는 되고 워커 단계에서 bun-missing-after-install 로 멈춘다",
79
+ "근거": "2026-09-06 실측 — Bun 을 손으로 깔고 다시 돌려서 통과했다" },
80
+ { "제목": "VibeSec 저장소 이름의 c 가 두 개다",
81
+ "내용": "BehiSec 로 적으면 404. BehiSecc 다",
82
+ "근거": "2026-09-06 실측 — GitHub API 가 404 를 냈다" }
83
+ ]
84
+ }
package/tail.mjs ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 「끝난 뒤 한 칸」 문안 — 정본은 `skillpack/tail_copy.mjs` 다.
3
+ * 사본이 여기 있는 이유는 하나뿐이다: npm 패키지는 폴더 밖을 import 할 수 없다.
4
+ * 고칠 일이 생기면 정본을 먼저 고치고 여기로 옮긴다. 반대로 하지 마라.
5
+ *
6
+ * 규율: ① 끝난 뒤 딱 한 칸 · ② 못 깔았으면 안 띄운다 · ③ 파일을 안 남긴다
7
+ * ④ 고지 블록 금지 · ⑤ 첫 줄은 방금 한 일에서 이어져야 한다
8
+ * ★클로드킷·자비스 풀스택은 여기서 말하지 않는다 — 스킬팩 구매 이후다(2026-08-24 전략).
9
+ */
10
+ export const 랜딩 = "https://rebornlabs.kr/skillpack";
11
+ export const 링크 = (퍼널) => `${랜딩}?utm_source=installer&utm_medium=cli&utm_campaign=${퍼널}`;
12
+
13
+ export const TAIL = {
14
+ mem3: {
15
+ 이음: [
16
+ "방금 한 일은 **남이 공개해 둔 스킬 셋을 이 컴퓨터에 실제로 깐 것**입니다 — 저희 코드는 한 줄도 안 들어갑니다.",
17
+ "고르는 데 시간이 드는 건 스킬이 없어서가 아니라 **어느 게 실제로 도는지 몰라서**입니다.",
18
+ ],
19
+ 제목: "리본랩스 스킬팩",
20
+ 한줄: "같은 방식으로 골라 담은 48종을 한 줄로. 8,900원",
21
+ },
22
+ };
23
+
24
+ export const 마지막 = "목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.";