reborn-skills-all 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.
Files changed (3) hide show
  1. package/README.md +68 -0
  2. package/install.mjs +289 -0
  3. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # reborn-skills-all
2
+
3
+ 산 스킬팩을 **한 줄로 설치하고, 진짜 깔렸는지 확인까지** 합니다.
4
+
5
+ ```
6
+ npx reborn-skills-all@latest <토큰>
7
+ ```
8
+
9
+ 토큰은 결제 완료 화면과 메일에 있습니다. 아직 안 사셨다면 → <https://rebornlabs.kr/skillpack>
10
+
11
+ 클로드 코드 안에서라면 맨 앞에 `!` 를 붙이면 바로 실행됩니다.
12
+ 아니면 그냥 “이 명령 실행해 줘” 하고 붙여넣으셔도 됩니다.
13
+
14
+ ---
15
+
16
+ ## 이 설치기가 하는 일
17
+
18
+ 깃허브 주소 하나가 스킬 하나가 아닙니다. **하나를 깔면 스무 개가 딸려 옵니다.**
19
+
20
+ 추천 목록을 그대로 다 깔면 스킬이 **182개** 쌓이고, 그 이름과 설명 **97,830자**가
21
+ 클로드가 대화를 시작할 때마다 실립니다. 느려지고, 사용량이 빨리 닳습니다.
22
+ 아무도 그 말을 해 주지 않습니다 — 저희는 46개를 임시 폴더에 실제로 다 깔아 보고 셌습니다.
23
+
24
+ 이 설치기는 레포가 아니라 **스킬 단위로 골라서** 심습니다.
25
+
26
+ - 골라내고 — 안 깔리는 것, 설명이 너무 긴 것, 다른 도구가 있어야 도는 것을 뺍니다
27
+ - 한 줄로 깔고 — 여러 번 칠 필요가 없습니다
28
+ - **깔렸는지 다시 봅니다** — 안 붙은 것은 숨기지 않고 직접 깔 수 있게 주소를 드립니다
29
+
30
+ ## 다른 명령
31
+
32
+ ```
33
+ npx reborn-skills-all <토큰> --check 깔지 않고 지금 상태만 봅니다
34
+ npx reborn-skills-all <토큰> --only design 한 묶음만 (전부를 사신 경우)
35
+ npx reborn-skills-all --self-test 검사기 자체 시험 (설치 안 함)
36
+ ```
37
+
38
+ ## 자주 묻는 것
39
+
40
+ **몇 번까지 쓸 수 있나요**
41
+ 30일에 3번입니다. PC 를 바꾸셔도 됩니다.
42
+
43
+ **새 스킬이 늘어나면요**
44
+ 같은 명령을 다시 돌리시면 됩니다. 목록은 서버에서 받아 오므로,
45
+ 저희가 스킬을 더할 때마다 다시 돌릴 때 함께 깔립니다.
46
+
47
+ **깔았는데 클로드가 모릅니다**
48
+ 클로드 코드를 껐다 켜 주세요. 그래야 새 스킬을 다시 읽습니다.
49
+
50
+ ---
51
+
52
+ ## 스킬은 누구 것인가
53
+
54
+ **전부 각 제작자의 무료 오픈소스입니다.**
55
+
56
+ 이 설치기는 파일을 복사해 두거나 다시 배포하지 않습니다.
57
+ 각 스킬의 **공식 설치 명령을 대신 실행할 뿐**입니다. 그래서 버전이 뒤처지지 않고,
58
+ 만든 분들의 라이선스도 그대로 지켜집니다.
59
+
60
+ 만든 곳과 라이선스는 화면에, 그리고 설치된 각 스킬 폴더의 원문에 그대로 있습니다.
61
+ 저희가 받는 값은 스킬 값이 아니라 **골라 주고 · 깔아 주고 · 확인해 주는 일**에 대한 것입니다.
62
+
63
+ 전체 목록은 결제 없이도 <https://rebornlabs.kr/skillpack> 에서 보실 수 있고,
64
+ 직접 설치하는 명령도 거기 그대로 적어 두었습니다.
65
+
66
+ ---
67
+
68
+ REBORN LABS · 문의 <https://mobility.rebornlabs.kr/cs>
package/install.mjs ADDED
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 스킬팩 설치기 — **깔고, 진짜 깔렸는지 확인한다.**
4
+ *
5
+ * npx reborn-skills-all@latest <토큰> 산 것을 전부 설치
6
+ * npx reborn-skills-all <토큰> --check 설치하지 않고 지금 상태만 본다
7
+ * npx reborn-skills-all <토큰> --only design 묶음 하나만
8
+ * npx reborn-skills-all --self-test 검사기 자체 시험(설치 안 함)
9
+ *
10
+ * ★★파는 것은 스킬이 아니라 **설치 대행**이다.
11
+ * 스킬은 전부 남이 만든 무료 오픈소스다. 우리가 하는 일은 셋이다 —
12
+ * 골라내고, 한 줄로 깔고, **깔렸는지 다시 본다.**
13
+ *
14
+ * ★★파일을 담아 오지 않는다(벤더링 금지).
15
+ * 각 스킬의 공식 설치 명령(`skills add <레포>`)을 부른다. 버전이 뒤처지지 않고,
16
+ * 남의 저작물을 재배포하는 일이 아예 생기지 않는다. 만든 곳과 라이선스는 화면에 적는다.
17
+ *
18
+ * ★★이름표를 만들지 않는다 — **폴더 변화로 잡는다.**
19
+ * 레포 46개가 각각 무슨 이름의 스킬을 심는지 우리는 모른다. 이름표를 손으로 적어 두면
20
+ * 상대가 이름을 바꾸는 순간 조용히 어긋나고, 우리는 "설치 실패"라고 거짓말하게 된다.
21
+ * 그래서 **설치 전후로 스킬 폴더를 찍어 새로 생긴 것**을 그 레포의 산출물로 본다.
22
+ * 덤으로 `skills list -g` 의 `Source:` 줄을 읽어 **이미 깔린 레포**도 알아낸다.
23
+ *
24
+ * ★★종료코드로 판정하지 않는다.
25
+ * 설치 명령이 0 으로 끝나도 안 붙어 있을 수 있고, 0이 아니어도 붙어 있을 수 있다.
26
+ * 끝나고 나서 **실제로 파일이 생겼는지**를 다시 본다.
27
+ */
28
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
29
+ import { spawnSync } from "node:child_process";
30
+ import { homedir } from "node:os";
31
+ import { join } from "node:path";
32
+
33
+ const HOME = homedir();
34
+ const 인자 = process.argv.slice(2);
35
+ const 확인만 = 인자.includes("--check");
36
+ const 자기시험 = 인자.includes("--self-test");
37
+ const 하나만 = (() => { const i = 인자.indexOf("--only"); return i >= 0 ? 인자[i + 1] : null; })();
38
+ const 토큰 = 인자.find((a) => !a.startsWith("--") && a !== 하나만) || process.env.REBORN_SKILLPACK_TOKEN || "";
39
+
40
+ const 서버 = process.env.REBORN_SKILLPACK_ORIGIN || "https://mobility.rebornlabs.kr";
41
+
42
+ const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
43
+
44
+ /* ── 실행 도구 ──────────────────────────────────────────────────────────── */
45
+ // ★윈도우에서 `npx` 는 `npx.CMD` 다. shell 없이 부르면 EINVAL 로 튄다.
46
+ const 윈도우 = process.platform === "win32";
47
+ const 셸필요 = () => 윈도우;
48
+
49
+ function 돌리기(args, timeout = 600000) {
50
+ return spawnSync("npx", args, {
51
+ encoding: "utf8", timeout, windowsHide: true, shell: 셸필요(),
52
+ // ★설치기가 되물으면 stdin 이 없으니 바로 끝난다. 붙잡고 있지 않는다.
53
+ input: "",
54
+ });
55
+ }
56
+
57
+ /* ── 스킬 폴더 ─────────────────────────────────────────────────────────── */
58
+ const 스킬폴더들 = [join(HOME, ".claude", "skills"), join(HOME, ".agents", "skills")];
59
+
60
+ /** 지금 깔려 있는 스킬 폴더 이름 전부. */
61
+ export function 폴더찍기() {
62
+ const out = new Set();
63
+ for (const base of 스킬폴더들) {
64
+ if (!existsSync(base)) continue;
65
+ let 목록 = [];
66
+ try { 목록 = readdirSync(base); } catch { continue; }
67
+ for (const n of 목록) {
68
+ try { if (statSync(join(base, n)).isDirectory()) out.add(n); } catch { /* 지나간다 */ }
69
+ }
70
+ }
71
+ return out;
72
+ }
73
+
74
+ /** 이 이름의 스킬이 파일로 존재하고 머리말이 온전한가. */
75
+ export function 파일로있나(이름) {
76
+ for (const base of 스킬폴더들) {
77
+ const p = join(base, 이름, "SKILL.md");
78
+ if (!existsSync(p)) continue;
79
+ let t = "";
80
+ try { t = readFileSync(p, "utf8"); } catch { return { ok: false, 이유: "SKILL.md 를 읽지 못했다" }; }
81
+ // ★있다고 다 된 게 아니다. 머리말이 깨지면 클로드가 스킬로 못 읽는다.
82
+ if (!/^---[\s\S]*?\bname\s*:/m.test(t)) return { ok: false, 이유: "SKILL.md 머리말에 name 이 없다" };
83
+ if (t.trim().length < 40) return { ok: false, 이유: "SKILL.md 가 사실상 비어 있다" };
84
+ return { ok: true, 경로: p };
85
+ }
86
+ return { ok: false, 이유: "SKILL.md 가 없다" };
87
+ }
88
+
89
+ /* ── `skills list -g` 읽기 ─────────────────────────────────────────────── */
90
+ /**
91
+ * 목록 출력에서 **이미 깔린 레포**를 뽑는다. 출력은 이런 꼴이다:
92
+ *
93
+ * agent-browser ~\.claude\skills\agent-browser
94
+ * Agents: Claude Code Source: vercel-labs/agent-browser
95
+ *
96
+ * ★색 코드를 먼저 걷는다. 안 걷으면 이름에 `\x1b[36m` 가 붙어 전건 어긋난다.
97
+ */
98
+ export function 레포뽑기(텍스트) {
99
+ const 깨끗 = String(텍스트 || "").replace(/\x1b\[[0-9;]*m/g, "");
100
+ const out = new Set();
101
+ for (const m of 깨끗.matchAll(/Source:\s*([\w.-]+\/[\w.-]+)/g)) out.add(m[1].toLowerCase());
102
+ return out;
103
+ }
104
+
105
+ let _목록캐시 = null;
106
+ function 설치목록(다시 = false) {
107
+ if (_목록캐시 && !다시) return _목록캐시;
108
+ const r = 돌리기(["-y", "skills@latest", "list", "-g"], 180000);
109
+ const 원문 = String(r.stdout || "") + String(r.stderr || "");
110
+ _목록캐시 = { ok: r.status === 0, 레포들: 레포뽑기(원문) };
111
+ return _목록캐시;
112
+ }
113
+
114
+ /* ── 서버에서 산 것을 받아온다 ──────────────────────────────────────────
115
+ ★목록을 패키지 안에 박아 두지 않는다. 박아 두면
116
+ ① 구성이 바뀔 때마다 npm 을 다시 내야 하고
117
+ ② 돈을 안 낸 사람도 `npx` 만 치면 목록을 통째로 가져간다.
118
+ 서버가 토큰을 보고 **산 만큼만** 돌려준다. */
119
+ async function 받아오기(토큰) {
120
+ let res;
121
+ try {
122
+ res = await fetch(`${서버}/api/skillpack-verify`, {
123
+ method: "POST",
124
+ headers: { "content-type": "application/json" },
125
+ body: JSON.stringify({ token: 토큰, only: 하나만 || null, v: 1 }),
126
+ });
127
+ } catch (e) {
128
+ return { ok: false, 이유: `서버에 닿지 못했습니다(${e?.message || "네트워크"}). 잠시 뒤 다시 시도해 주세요.` };
129
+ }
130
+
131
+ if (!res.ok) {
132
+ if (res.status === 404 || res.status === 403) {
133
+ return { ok: false, 이유: "토큰이 확인되지 않았습니다. 결제 완료 화면의 명령을 그대로 다시 붙여넣어 주세요." };
134
+ }
135
+ if (res.status === 429) {
136
+ return { ok: false, 이유: "이 토큰의 사용 횟수를 다 썼습니다. 고객센터로 알려 주시면 다시 열어 드립니다 — https://mobility.rebornlabs.kr/cs" };
137
+ }
138
+ return { ok: false, 이유: `서버가 ${res.status} 을 돌려주었습니다. 잠시 뒤 다시 시도해 주세요.` };
139
+ }
140
+ try { return await res.json(); }
141
+ catch { return { ok: false, 이유: "서버 응답을 읽지 못했습니다. 잠시 뒤 다시 시도해 주세요." }; }
142
+ }
143
+
144
+ /* ── 선행 조건 ─────────────────────────────────────────────────────────── */
145
+ export function 선행조건() {
146
+ const 탈 = [];
147
+ const major = Number(process.versions.node.split(".")[0]);
148
+ if (major < 18) 탈.push(`Node 가 ${process.versions.node} 입니다. 18 이상이 필요합니다 → https://nodejs.org`);
149
+ const r = spawnSync("npx", ["--version"], { encoding: "utf8", timeout: 60000, windowsHide: true, shell: 셸필요() });
150
+ if (r.status !== 0) 탈.push("npx 를 실행하지 못했습니다. Node.js 를 다시 설치해 주세요.");
151
+ if (!existsSync(join(HOME, ".claude"))) 탈.push("~/.claude 폴더가 없습니다. 클로드 코드를 한 번 실행한 뒤 다시 돌려 주세요.");
152
+ return 탈;
153
+ }
154
+
155
+ /* ── 하나 설치하기 ─────────────────────────────────────────────────────── */
156
+ function 하나설치(s, 이미깔린레포) {
157
+ if (이미깔린레포.has(String(s.repo).toLowerCase())) return { 등급: "이미", 설명: "이미 깔려 있습니다" };
158
+
159
+ const 전 = 폴더찍기();
160
+ const args = ["-y", "skills@latest", "add", s.repo];
161
+ if (s.skill) args.push("--skill", s.skill); // 레포 하나에 스킬이 여럿인 경우
162
+ args.push("-g", "-a", "claude-code", "-y");
163
+ const r = 돌리기(args, 300000);
164
+
165
+ const 새로 = [...폴더찍기()].filter((n) => !전.has(n));
166
+
167
+ if (새로.length === 0) {
168
+ // ★종료코드가 0 이어도 여기서 잡힌다 — 아무것도 안 생겼으면 안 깔린 것이다.
169
+ const 끝줄 = (String(r.stderr || "") + String(r.stdout || ""))
170
+ .replace(/\x1b\[[0-9;]*m/g, "").split(/\r?\n/).filter((x) => x.trim()).slice(-1)[0] || "";
171
+ return { 등급: "실패", 설명: 끝줄.trim().slice(0, 110) || "새로 생긴 스킬이 없습니다" };
172
+ }
173
+
174
+ // ★생겼다고 다 된 게 아니다. 머리말까지 본다.
175
+ const 성한것 = 새로.filter((n) => 파일로있나(n).ok);
176
+ if (성한것.length === 0) return { 등급: "반쪽", 설명: `폴더는 생겼는데 SKILL.md 가 성하지 않습니다 (${새로.join(", ")})` };
177
+ return { 등급: "완료", 설명: 성한것.join(", "), 심은것: 성한것 };
178
+ }
179
+
180
+ /* ── 자기시험 ──────────────────────────────────────────────────────────
181
+ ★검사기는 **양방향으로** 시험한다. 전건 통과도 전건 실패만큼 고장 신호다. */
182
+ if (자기시험) {
183
+ const 시험 = [];
184
+ const T = (n, ok) => 시험.push([n, !!ok]);
185
+
186
+ const 샘플 = [
187
+ "\x1b[1mGlobal Skills\x1b[0m",
188
+ "\x1b[36magent-browser \x1b[0m \x1b[38;5;102m~\\.claude\\skills\\agent-browser\x1b[0m",
189
+ " \x1b[38;5;102mAgents:\x1b[0m Claude Code \x1b[38;5;102mSource:\x1b[0m vercel-labs/agent-browser",
190
+ "\x1b[36mhumanizer \x1b[0m \x1b[38;5;102m~\\.claude\\skills\\humanizer\x1b[0m",
191
+ " Agents: Claude Code Source: blader/humanizer",
192
+ ].join("\n");
193
+ const 레포들 = 레포뽑기(샘플);
194
+
195
+ //: 있는 것을 있다고 하는가
196
+ T("Source 줄에서 레포를 뽑는다", 레포들.has("vercel-labs/agent-browser") && 레포들.has("blader/humanizer"));
197
+ T("★색 코드가 레포 이름에 섞이지 않는다", ![...레포들].some((x) => x.includes("[")));
198
+ //: ★없는 것을 없다고 하는가 — 이쪽을 빼면 "전부 있다"고 답하는 고장을 못 잡는다
199
+ T("★없는 레포는 없다고 한다", !레포들.has("obra/superpowers"));
200
+ T("★빈 입력에서 아무것도 뽑지 않는다", 레포뽑기("").size === 0 && 레포뽑기(null).size === 0);
201
+ T("★Source 가 없는 출력에서는 아무것도 뽑지 않는다", 레포뽑기("agent-browser ~/.claude/skills/agent-browser").size === 0);
202
+ T("★색 코드를 안 걷으면 못 뽑는다(그래서 걷는다)", 레포뽑기(샘플.replace(/Source:/g, "\x1b[1mSource:\x1b[0m")).size === 2);
203
+
204
+ T("★없는 스킬은 파일로도 없다", !파일로있나("이런건없다-" + Date.now()).ok);
205
+ const 폴더 = 폴더찍기();
206
+ T("폴더 찍기가 집합을 돌려준다", 폴더 instanceof Set);
207
+ // ★기계 상태에 기대지 않는다 — 스킬이 하나도 없는 PC 에서도 이 시험은 통과해야 한다.
208
+ const 아무거나 = [...폴더].find((n) => 파일로있나(n).ok);
209
+ T("이미 있는 스킬은 파일로 잡힌다(없으면 건너뜀)", !아무거나 || 파일로있나(아무거나).ok);
210
+
211
+ const 실패 = 시험.filter(([, ok]) => !ok);
212
+ for (const [n, ok] of 시험) console.log(`${ok ? c.g + " ✓" : c.r + " ✗"} ${n}${c[0]}`);
213
+ console.log(실패.length ? `${c.r}${c.b}\n ${실패.length}건 실패${c[0]}\n` : `${c.g}${c.b}\n ${시험.length}건 전부 통과${c[0]}\n`);
214
+ process.exit(실패.length ? 1 : 0);
215
+ }
216
+
217
+ /* ── 본 실행 ──────────────────────────────────────────────────────────── */
218
+ const 줄 = (s = "") => console.log(s);
219
+
220
+ async function main() {
221
+ 줄(`\n${c.b}${c.cy} 스킬팩 설치기${c[0]} ${c.d}REBORN LABS${c[0]}\n`);
222
+
223
+ const 탈 = 선행조건();
224
+ if (탈.length) {
225
+ for (const t of 탈) 줄(`${c.r} ✗ ${t}${c[0]}`);
226
+ 줄("");
227
+ process.exit(1);
228
+ }
229
+
230
+ if (!토큰) {
231
+ 줄(`${c.y} 결제 완료 화면의 명령을 그대로 붙여넣어 주세요.${c[0]}`);
232
+ 줄(`${c.d} 예) npx reborn-skills-all@latest a3f9c21b${c[0]}`);
233
+ 줄(`${c.d}\n 아직 안 사셨다면 → https://rebornlabs.kr/skillpack${c[0]}\n`);
234
+ process.exit(1);
235
+ }
236
+
237
+ const 받은것 = await 받아오기(토큰);
238
+ if (!받은것.ok) { 줄(`${c.r} ✗ ${받은것.이유}${c[0]}\n`); process.exit(1); }
239
+
240
+ const 스킬들 = 받은것.skills || [];
241
+ if (!스킬들.length) { 줄(`${c.r} ✗ 받을 스킬 목록이 비어 있습니다. 고객센터로 알려 주세요 — https://mobility.rebornlabs.kr/cs${c[0]}\n`); process.exit(1); }
242
+ 줄(` ${c.b}${받은것.packLabel || "스킬팩"}${c[0]} ${c.d}${스킬들.length}종${c[0]}\n`);
243
+
244
+ const 목록 = 설치목록();
245
+
246
+ if (확인만) {
247
+ let 있음 = 0;
248
+ for (const s of 스킬들) {
249
+ const 깔림 = 목록.레포들.has(String(s.repo).toLowerCase());
250
+ if (깔림) 있음++;
251
+ 줄(` ${깔림 ? c.g + "●" : c.d + "○"}${c[0]} ${String(s.title || s.repo).padEnd(24)} ${c.d}${s.repo}${c[0]}`);
252
+ }
253
+ 줄(`\n ${있음} / ${스킬들.length} 깔려 있습니다.\n`);
254
+ process.exit(0);
255
+ }
256
+
257
+ const 결과 = [];
258
+ let i = 0;
259
+ for (const s of 스킬들) {
260
+ i++;
261
+ process.stdout.write(` ${c.d}[${String(i).padStart(2)}/${스킬들.length}]${c[0]} ${String(s.title || s.repo).padEnd(22)} `);
262
+ const r = 하나설치(s, 목록.레포들);
263
+ 결과.push({ s, r });
264
+ const 색 = { 완료: c.g, 이미: c.d, 반쪽: c.y, 실패: c.r }[r.등급] || c.d;
265
+ 줄(`${색}${r.등급}${c[0]} ${c.d}${r.설명}${c[0]}`);
266
+ }
267
+
268
+ const 셈 = (등급) => 결과.filter((x) => x.r.등급 === 등급).length;
269
+ 줄("");
270
+ 줄(` ${c.g}완료 ${셈("완료")}${c[0]} ${c.d}이미 ${셈("이미")}${c[0]} ${c.y}반쪽 ${셈("반쪽")}${c[0]} ${c.r}실패 ${셈("실패")}${c[0]}`);
271
+
272
+ /* ★못 깐 것을 숨기지 않는다. 숨기면 고객은 "다 됐다"고 믿고 넘어갔다가
273
+ 나중에 "왜 안 되지"를 만난다 — 그게 환불 사유가 된다. */
274
+ const 탈난것 = 결과.filter((x) => x.r.등급 === "실패" || x.r.등급 === "반쪽");
275
+ if (탈난것.length) {
276
+ 줄(`\n ${c.y}아래는 자동으로 깔리지 않았습니다. 직접 깔 수 있게 주소를 남깁니다.${c[0]}`);
277
+ for (const { s } of 탈난것) 줄(` ${c.d}·${c[0]} ${s.title || s.repo} ${c.cy}https://github.com/${s.repo}${c[0]}`);
278
+ 줄(`\n ${c.d}잘 안 되면 고객센터로 알려 주세요 → https://mobility.rebornlabs.kr/cs${c[0]}`);
279
+ }
280
+
281
+ 줄(`\n ${c.d}이 설치기는 파일을 복사해 오지 않고 각 스킬의 공식 설치 명령을 부릅니다.`);
282
+ 줄(` 만든 곳과 라이선스는 각 스킬 폴더의 원문에 그대로 있습니다.${c[0]}`);
283
+ 줄(`\n ${c.b}클로드 코드를 껐다 켜면 새 스킬이 잡힙니다.${c[0]}\n`);
284
+
285
+ // ★하나도 못 깔았을 때만 비0 으로 끝낸다. 일부 실패는 위에 그대로 적어 두었다.
286
+ process.exit(셈("완료") === 0 && 셈("이미") === 0 ? 1 : 0);
287
+ }
288
+
289
+ main().catch((e) => { console.error(`${c.r} ✗ ${e?.message || e}${c[0]}\n`); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "reborn-skills-all",
3
+ "version": "1.0.0",
4
+ "description": "산 스킬팩을 한 줄로 설치하고, 진짜 깔렸는지 확인까지 합니다 — 결제 후 받은 토큰이 필요합니다",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "skills",
9
+ "agent-skills",
10
+ "installer",
11
+ "skillpack"
12
+ ],
13
+ "homepage": "https://rebornlabs.kr/skillpack",
14
+ "bugs": {
15
+ "url": "https://mobility.rebornlabs.kr/cs"
16
+ },
17
+ "license": "MIT",
18
+ "author": "REBORN LABS",
19
+ "type": "module",
20
+ "bin": {
21
+ "reborn-skills-all": "install.mjs"
22
+ },
23
+ "files": [
24
+ "install.mjs",
25
+ "README.md"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "scripts": {
31
+ "test": "node install.mjs --self-test"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/reborn-labs-kr/reborn-skills-all.git"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ }
40
+ }