reborn-skills-all 1.2.2 → 1.3.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/install.mjs +188 -49
- package/package.json +1 -1
package/install.mjs
CHANGED
|
@@ -37,6 +37,8 @@ const 인자 = process.argv.slice(2);
|
|
|
37
37
|
const 확인만 = 인자.includes("--check");
|
|
38
38
|
const 자기시험 = 인자.includes("--self-test");
|
|
39
39
|
const 하나만 = (() => { const i = 인자.indexOf("--only"); return i >= 0 ? 인자[i + 1] : null; })();
|
|
40
|
+
/** `--lang=en` · `--lang=ko`. 안 주면 이 컴퓨터의 로케일로 정한다(→ `말정하기`). */
|
|
41
|
+
const 말강제 = (인자.find((a) => a.startsWith("--lang=")) || "").slice(7).toLowerCase();
|
|
40
42
|
/**
|
|
41
43
|
* 주문번호(토큰). 세 가지 형태를 다 받는다.
|
|
42
44
|
*
|
|
@@ -61,7 +63,127 @@ const 토큰 = (() => {
|
|
|
61
63
|
|
|
62
64
|
const 서버 = process.env.REBORN_SKILLPACK_ORIGIN || "https://mobility.rebornlabs.kr";
|
|
63
65
|
/** 막혔을 때 돌아갈 곳. **한 곳에만 적는다** — 여러 군데 적으면 주소가 갈린다. */
|
|
64
|
-
|
|
66
|
+
/**
|
|
67
|
+
* 어느 말로 말할 것인가. (2026-09-20 · 영어권 1차)
|
|
68
|
+
*
|
|
69
|
+
* ★★**이 설치기가 영어권 고객이 실제로 받는 것이다.** 인계 §H 가 손본 것은
|
|
70
|
+
* `claudekit/skills2kit`(스킬팩2 · `reborn-skills2`) 이고, $8 스킬팩이 보내는 명령은
|
|
71
|
+
* `reborn-skills-all` — **이 파일**이다. 그래서 여기에도 갈래가 있어야 한다.
|
|
72
|
+
* ★고객 컴퓨터의 로케일이 곧 그 사람이 읽는 말이다. 서버에 묻지 않는다 —
|
|
73
|
+
* 토큰 검증에는 국경이 없고, 그쪽은 고치지 않는다(인계 §C).
|
|
74
|
+
* ★못 읽으면 한국어로 떨어진다. 지금 사는 사람의 절대다수가 한국 고객이라,
|
|
75
|
+
* 판정이 실패했을 때 덜 틀리는 쪽이 그쪽이다.
|
|
76
|
+
*/
|
|
77
|
+
export function 말정하기(강제) {
|
|
78
|
+
if (강제 === "en" || 강제 === "ko") return 강제;
|
|
79
|
+
try {
|
|
80
|
+
return /^ko\b/i.test(Intl.DateTimeFormat().resolvedOptions().locale || "") ? "ko" : "en";
|
|
81
|
+
} catch { return "ko"; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 화면에 찍는 말. **색은 여기 넣지 않는다** — 부르는 쪽이 입힌다.
|
|
86
|
+
*
|
|
87
|
+
* ★한국어 문구는 **한 글자도 바꾸지 않았다.** 옮겨 담기만 했다.
|
|
88
|
+
* 말을 고치는 것과 갈래를 내는 것을 같이 하면 무엇이 깨졌는지 못 가린다.
|
|
89
|
+
* ★영문은 번역이 아니라 **그 자리에서 할 말**이다. 주소부터 다르다 —
|
|
90
|
+
* 영문 고객을 한국어 안내 페이지로 보내면 받으러 왔다가 돌아선다(인계 §6-2⑦).
|
|
91
|
+
*/
|
|
92
|
+
const 글표 = {
|
|
93
|
+
ko: {
|
|
94
|
+
안내주소: "https://rebornlabs.kr/skillpack-guide",
|
|
95
|
+
착지주소: "https://rebornlabs.kr/skillpack",
|
|
96
|
+
제목: "스킬팩 설치기",
|
|
97
|
+
팩기본: "스킬팩",
|
|
98
|
+
종: (n) => `${n}종`,
|
|
99
|
+
토큰없음: "결제 완료 화면의 명령을 그대로 붙여넣어 주세요.",
|
|
100
|
+
토큰예: "예) npx --yes reborn-skills-all@latest a3f9c21b",
|
|
101
|
+
아직안샀다면: (주소) => `아직 안 사셨다면 → ${주소}`,
|
|
102
|
+
쓰는법: (주소) => `쓰는 법 → ${주소}`,
|
|
103
|
+
목록빔: "받을 스킬 목록이 비어 있습니다. 고객센터로 알려 주세요 — https://mobility.rebornlabs.kr/cs",
|
|
104
|
+
깔려있다: (있음, 전체) => `${있음} / ${전체} 깔려 있습니다.`,
|
|
105
|
+
등급: { 완료: "완료", 이미: "이미", 반쪽: "반쪽", 실패: "실패" },
|
|
106
|
+
셈: { 완료: (n) => `완료 ${n}`, 이미: (n) => `이미 ${n}`, 반쪽: (n) => `반쪽 ${n}`, 실패: (n) => `실패 ${n}` },
|
|
107
|
+
탈난것머리: "아래는 자동으로 깔리지 않았습니다. 직접 깔 수 있게 주소를 남깁니다.",
|
|
108
|
+
막히면: "잘 안 되면 고객센터로 알려 주세요 → https://mobility.rebornlabs.kr/cs",
|
|
109
|
+
라이선스1: "이 설치기는 파일을 복사해 오지 않고 각 스킬의 공식 설치 명령을 부릅니다.",
|
|
110
|
+
라이선스2: "만든 곳과 라이선스는 각 스킬 폴더의 원문에 그대로 있습니다.",
|
|
111
|
+
재시작: "클로드 코드를 껐다 켜면 새 스킬이 잡힙니다.",
|
|
112
|
+
안내심음1: "반복되는 일을 찾아 주는 안내를 함께 넣었습니다.",
|
|
113
|
+
안내심음2: (자리) => `${자리} (필요 없으면 지우셔도 됩니다)`,
|
|
114
|
+
/* 받아오기 */
|
|
115
|
+
못닿음: (왜) => `서버에 닿지 못했습니다(${왜}). 잠시 뒤 다시 시도해 주세요.`,
|
|
116
|
+
토큰못찾음: "토큰이 확인되지 않았습니다. 결제 완료 화면의 명령을 그대로 다시 붙여넣어 주세요.",
|
|
117
|
+
횟수다씀: "이 토큰의 사용 횟수를 다 썼습니다. 고객센터로 알려 주시면 다시 열어 드립니다 — https://mobility.rebornlabs.kr/cs",
|
|
118
|
+
서버코드: (코드) => `서버가 ${코드} 을 돌려주었습니다. 잠시 뒤 다시 시도해 주세요.`,
|
|
119
|
+
응답못읽음: "서버 응답을 읽지 못했습니다. 잠시 뒤 다시 시도해 주세요.",
|
|
120
|
+
응답이상: "서버 응답이 예상과 다릅니다. 잠시 뒤 다시 시도해 주시고, 그래도 같으면 "
|
|
121
|
+
+ "고객센터로 알려 주세요 — https://mobility.rebornlabs.kr/cs",
|
|
122
|
+
/* 선행조건 */
|
|
123
|
+
노드낮음: (판) => `Node 가 ${판} 입니다. 18 이상이 필요합니다 → https://nodejs.org`,
|
|
124
|
+
npx못돎: "npx 를 실행하지 못했습니다. Node.js 를 다시 설치해 주세요.",
|
|
125
|
+
클로드없음: "~/.claude 폴더가 없습니다. 클로드 코드를 한 번 실행한 뒤 다시 돌려 주세요.",
|
|
126
|
+
git없음: "git 이 없습니다. 스킬을 내려받는 데 git 이 꼭 필요합니다 — 이것 없이는 한 종도 깔리지 않습니다.",
|
|
127
|
+
git윈: " 설치: 명령 프롬프트에 winget install --id Git.Git -e --source winget\n"
|
|
128
|
+
+ " ★깐 뒤에는 이 검은 창을 닫고 새로 열어야 합니다. 열려 있던 창은 옛 환경을 그대로 들고 있습니다.",
|
|
129
|
+
git맥: " 설치: xcode-select --install (또는 brew install git)",
|
|
130
|
+
git리눅스: " 설치: sudo apt install git (또는 각 배포판의 패키지 관리자)",
|
|
131
|
+
/* 하나설치 */
|
|
132
|
+
이미깔림: "이미 깔려 있습니다",
|
|
133
|
+
새것없음: "새로 생긴 스킬이 없습니다",
|
|
134
|
+
반쪽설명: (들) => `폴더는 생겼는데 SKILL.md 가 성하지 않습니다 (${들})`,
|
|
135
|
+
},
|
|
136
|
+
en: {
|
|
137
|
+
안내주소: "https://rebornlabs.kr/en/skillpack-guide",
|
|
138
|
+
착지주소: "https://rebornlabs.kr/en/skillpack",
|
|
139
|
+
제목: "Skill Pack Installer",
|
|
140
|
+
팩기본: "Skill Pack",
|
|
141
|
+
종: (n) => `${n} skills`,
|
|
142
|
+
토큰없음: "Paste the command from your confirmation page exactly as it is.",
|
|
143
|
+
토큰예: "e.g. npx --yes reborn-skills-all@latest a3f9c21b",
|
|
144
|
+
아직안샀다면: (주소) => `Haven't bought it yet → ${주소}`,
|
|
145
|
+
쓰는법: (주소) => `How to use it → ${주소}`,
|
|
146
|
+
목록빔: "Your skill list came back empty. Please let us know — https://mobility.rebornlabs.kr/cs",
|
|
147
|
+
깔려있다: (있음, 전체) => `${있음} of ${전체} are installed.`,
|
|
148
|
+
등급: { 완료: "done", 이미: "already", 반쪽: "partial", 실패: "failed" },
|
|
149
|
+
셈: { 완료: (n) => `${n} done`, 이미: (n) => `${n} already`, 반쪽: (n) => `${n} partial`, 실패: (n) => `${n} failed` },
|
|
150
|
+
탈난것머리: "These did not install on their own. Here is where to get each one directly.",
|
|
151
|
+
막히면: "Still stuck? Tell us and we will help → https://mobility.rebornlabs.kr/cs",
|
|
152
|
+
라이선스1: "This installer copies no files. It runs each skill's own official install command.",
|
|
153
|
+
라이선스2: "The author and license of each skill stay in that skill's folder, unchanged.",
|
|
154
|
+
재시작: "Restart Claude Code and the new skills come up.",
|
|
155
|
+
안내심음1: "We also added a short note that helps Claude spot work worth automating.",
|
|
156
|
+
안내심음2: (자리) => `${자리} (delete it if you don't want it)`,
|
|
157
|
+
/* 받아오기 */
|
|
158
|
+
못닿음: (왜) => `Could not reach the server (${왜}). Please try again in a moment.`,
|
|
159
|
+
토큰못찾음: "We could not verify that order number. Please paste the command from your confirmation page again, exactly as it is.",
|
|
160
|
+
횟수다씀: "This order number has used up its installs. Tell us and we will open it again — https://mobility.rebornlabs.kr/cs",
|
|
161
|
+
서버코드: (코드) => `The server returned ${코드}. Please try again in a moment.`,
|
|
162
|
+
응답못읽음: "Could not read the server's reply. Please try again in a moment.",
|
|
163
|
+
응답이상: "The server's reply was not what we expected. Please try again in a moment, and if it "
|
|
164
|
+
+ "keeps happening, tell us — https://mobility.rebornlabs.kr/cs",
|
|
165
|
+
/* 선행조건 */
|
|
166
|
+
노드낮음: (판) => `You are on Node ${판}. Version 18 or newer is required → https://nodejs.org`,
|
|
167
|
+
npx못돎: "Could not run npx. Please reinstall Node.js.",
|
|
168
|
+
클로드없음: "There is no ~/.claude folder yet. Run Claude Code once, then run this again.",
|
|
169
|
+
git없음: "git is not installed. Skills are downloaded with git — without it, nothing will install.",
|
|
170
|
+
git윈: " Install: run winget install --id Git.Git -e --source winget\n"
|
|
171
|
+
+ " Note: close this window and open a new one afterwards. An open window keeps the old environment.",
|
|
172
|
+
git맥: " Install: xcode-select --install (or brew install git)",
|
|
173
|
+
git리눅스: " Install: sudo apt install git (or your distribution's package manager)",
|
|
174
|
+
/* 하나설치 */
|
|
175
|
+
이미깔림: "already installed",
|
|
176
|
+
새것없음: "no new skill appeared",
|
|
177
|
+
반쪽설명: (들) => `the folder appeared but its SKILL.md is not intact (${들})`,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/** 이번 실행이 쓰는 말. */
|
|
182
|
+
const 말 = 말정하기(말강제);
|
|
183
|
+
/** 화면 문구. 부르는 쪽은 `G.무엇` 으로 쓴다. */
|
|
184
|
+
const G = 글표[말];
|
|
185
|
+
|
|
186
|
+
const 안내 = G.안내주소;
|
|
65
187
|
|
|
66
188
|
const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
|
|
67
189
|
|
|
@@ -162,21 +284,21 @@ async function 받아오기(토큰) {
|
|
|
162
284
|
body: JSON.stringify({ token: 토큰, only: 하나만 || null, v: 1 }),
|
|
163
285
|
});
|
|
164
286
|
} catch (e) {
|
|
165
|
-
return { ok: false, 이유:
|
|
287
|
+
return { ok: false, 이유: G.못닿음(e?.message || (말 === "en" ? "network" : "네트워크")) };
|
|
166
288
|
}
|
|
167
289
|
|
|
168
290
|
if (!res.ok) {
|
|
169
291
|
if (res.status === 404 || res.status === 403) {
|
|
170
|
-
return { ok: false, 이유:
|
|
292
|
+
return { ok: false, 이유: G.토큰못찾음 };
|
|
171
293
|
}
|
|
172
294
|
if (res.status === 429) {
|
|
173
|
-
return { ok: false, 이유:
|
|
295
|
+
return { ok: false, 이유: G.횟수다씀 };
|
|
174
296
|
}
|
|
175
|
-
return { ok: false, 이유:
|
|
297
|
+
return { ok: false, 이유: G.서버코드(res.status) };
|
|
176
298
|
}
|
|
177
299
|
let 몸통;
|
|
178
300
|
try { 몸통 = await res.json(); }
|
|
179
|
-
catch { return { ok: false, 이유:
|
|
301
|
+
catch { return { ok: false, 이유: G.응답못읽음 }; }
|
|
180
302
|
|
|
181
303
|
/* ★200 인데 모양이 다를 수 있다 — 배포가 어긋났거나, 중간의 프록시·와이파이 로그인 화면이
|
|
182
304
|
제 JSON 을 돌려줄 때가 그렇다. 그때 이 함수가 그 몸통을 그대로 돌려주면
|
|
@@ -185,12 +307,13 @@ async function 받아오기(토큰) {
|
|
|
185
307
|
★★돈을 낸 직후의 손님에게 이런 화면을 보이면 안 된다. 그게 이번 사고의 교훈이다.
|
|
186
308
|
(2026-08-25: 되받아 돌려 보다가 실제로 이 화면을 봤다.) */
|
|
187
309
|
if (!몸통 || typeof 몸통 !== "object" || 몸통.ok !== true) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
310
|
+
/* ★서버가 주는 `이유` 는 **한국어다.** 서버는 고치지 않는다(인계 §4 — 라이브 결제 경로).
|
|
311
|
+
그래서 영어로 도는 실행에서는 **읽을 수 없는 글을 그대로 띄우지 않고** 우리 문구로 바꾼다.
|
|
312
|
+
읽을 수 없는 안내는 안내가 아니라 막다른 길이다. */
|
|
313
|
+
if (몸통 && typeof 몸통.이유 === "string" && !(말 === "en" && /[가-힣]/.test(몸통.이유))) {
|
|
314
|
+
return { ok: false, 이유: 몸통.이유 };
|
|
315
|
+
}
|
|
316
|
+
return { ok: false, 이유: G.응답이상 };
|
|
194
317
|
}
|
|
195
318
|
return 몸통;
|
|
196
319
|
}
|
|
@@ -198,13 +321,13 @@ async function 받아오기(토큰) {
|
|
|
198
321
|
/* ── 선행 조건 ─────────────────────────────────────────────────────────── */
|
|
199
322
|
/* ★`git검사` 를 인자로 받는다. 시험이 **git 이 깔린 PC 에서도** 「없을 때」를 재려면
|
|
200
323
|
없는 상태를 실제로 만들 수 있어야 한다. 기본값은 진짜 명령이라 본 실행은 그대로다. */
|
|
201
|
-
export function 선행조건(git검사 = "git --version") {
|
|
324
|
+
export function 선행조건(git검사 = "git --version", T = G) {
|
|
202
325
|
const 탈 = [];
|
|
203
326
|
const major = Number(process.versions.node.split(".")[0]);
|
|
204
|
-
if (major < 18) 탈.push(
|
|
327
|
+
if (major < 18) 탈.push(T.노드낮음(process.versions.node));
|
|
205
328
|
const r = spawnSync("npx --version", { encoding: "utf8", timeout: 60000, windowsHide: true, shell: true });
|
|
206
|
-
if (r.status !== 0) 탈.push(
|
|
207
|
-
if (!existsSync(join(HOME, ".claude"))) 탈.push(
|
|
329
|
+
if (r.status !== 0) 탈.push(T.npx못돎);
|
|
330
|
+
if (!existsSync(join(HOME, ".claude"))) 탈.push(T.클로드없음);
|
|
208
331
|
/* ★★git 이 없으면 **48종이 전부 실패한다.** 속 도구인 `skills` 가 simple-git 으로
|
|
209
332
|
시스템 `git` 실행파일을 불러 레포를 clone 하기 때문이다. 윈도우에는 git 이 기본으로
|
|
210
333
|
깔려 있지 않다 — 그래서 이건 드문 일이 아니라 **흔한 첫 실행**이다.
|
|
@@ -214,28 +337,25 @@ export function 선행조건(git검사 = "git --version") {
|
|
|
214
337
|
const g = spawnSync(git검사, { encoding: "utf8", timeout: 60000, windowsHide: true, shell: true });
|
|
215
338
|
if (g.status !== 0) {
|
|
216
339
|
탈.push(
|
|
217
|
-
|
|
218
|
-
+ (process.platform === "win32"
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
: process.platform === "darwin"
|
|
222
|
-
? " 설치: xcode-select --install (또는 brew install git)"
|
|
223
|
-
: " 설치: sudo apt install git (또는 각 배포판의 패키지 관리자)")
|
|
340
|
+
T.git없음 + "\n"
|
|
341
|
+
+ (process.platform === "win32" ? T.git윈
|
|
342
|
+
: process.platform === "darwin" ? T.git맥
|
|
343
|
+
: T.git리눅스)
|
|
224
344
|
);
|
|
225
345
|
}
|
|
226
346
|
return 탈;
|
|
227
347
|
}
|
|
228
348
|
|
|
229
349
|
/* ── 하나 설치하기 ─────────────────────────────────────────────────────── */
|
|
230
|
-
function 하나설치(s,
|
|
350
|
+
function 하나설치(s, 이미깔린레포, T = G) {
|
|
231
351
|
/* ★★"이미 있나"는 **스킬 단위로** 본다. 레포 단위로 보면 안 된다.
|
|
232
352
|
레포 하나가 스킬을 여럿 심고(ponytail 은 셋, caveman 은 아홉) 우리는 그중 골라서 판다.
|
|
233
353
|
레포로 판정하면 **그 레포에서 스킬 하나만 갖고 있던 고객**이 나머지를 산 뒤에도
|
|
234
354
|
전부 "이미 깔려 있습니다"로 건너뛴다 — 돈을 내고 못 받는 것이고, 화면은 초록으로 뜬다.
|
|
235
355
|
★그래서 파일(폴더+SKILL.md)이 실제로 있는지를 본다. 레포 목록은 참고만 한다. */
|
|
236
|
-
if (s.skill && 파일로있나(s.skill).ok) return { 등급: "이미", 설명:
|
|
356
|
+
if (s.skill && 파일로있나(s.skill).ok) return { 등급: "이미", 설명: T.이미깔림 };
|
|
237
357
|
//: 스킬 이름을 모르는 항목(옛 응답)만 레포로 떨어뜨린다.
|
|
238
|
-
if (!s.skill && 이미깔린레포.has(String(s.repo).toLowerCase())) return { 등급: "이미", 설명:
|
|
358
|
+
if (!s.skill && 이미깔린레포.has(String(s.repo).toLowerCase())) return { 등급: "이미", 설명: T.이미깔림 };
|
|
239
359
|
|
|
240
360
|
const 전 = 폴더찍기();
|
|
241
361
|
const args = ["-y", "skills@latest", "add", s.repo];
|
|
@@ -249,12 +369,12 @@ function 하나설치(s, 이미깔린레포) {
|
|
|
249
369
|
// ★종료코드가 0 이어도 여기서 잡힌다 — 아무것도 안 생겼으면 안 깔린 것이다.
|
|
250
370
|
const 끝줄 = (String(r.stderr || "") + String(r.stdout || ""))
|
|
251
371
|
.replace(/\x1b\[[0-9;]*m/g, "").split(/\r?\n/).filter((x) => x.trim()).slice(-1)[0] || "";
|
|
252
|
-
return { 등급: "실패", 설명: 끝줄.trim().slice(0, 110) ||
|
|
372
|
+
return { 등급: "실패", 설명: 끝줄.trim().slice(0, 110) || T.새것없음 };
|
|
253
373
|
}
|
|
254
374
|
|
|
255
375
|
// ★생겼다고 다 된 게 아니다. 머리말까지 본다.
|
|
256
376
|
const 성한것 = 새로.filter((n) => 파일로있나(n).ok);
|
|
257
|
-
if (성한것.length === 0) return { 등급: "반쪽", 설명:
|
|
377
|
+
if (성한것.length === 0) return { 등급: "반쪽", 설명: T.반쪽설명(새로.join(", ")) };
|
|
258
378
|
|
|
259
379
|
/* ★부탁한 것이 그대로 깔렸으면 **이름을 다시 적지 않는다.**
|
|
260
380
|
왼쪽에 이미 그 이름이 있어서 `brainstorming 완료 brainstorming` 처럼 두 번 나온다 —
|
|
@@ -285,7 +405,16 @@ const 이름 = (s) => String(s.skill || s.repo);
|
|
|
285
405
|
★이 주석에도 별과 슬래시를 붙여 쓰지 않는다. 그 조합은 주석을 거기서 끝내 버린다
|
|
286
406
|
(같은 사고를 이 자리에서 한 번 더 냈다).
|
|
287
407
|
split/join 은 이스케이프가 없어 그 사고가 원천적으로 안 난다. */
|
|
288
|
-
|
|
408
|
+
/**
|
|
409
|
+
* 스킬 한 줄 설명.
|
|
410
|
+
*
|
|
411
|
+
* ★★**서버는 `ko` 만 준다.** 영어로 도는데 `ko` 를 찍으면 읽을 수 없는 줄이 48개 내려간다.
|
|
412
|
+
* 그래서 영어일 때는 `en` 이 있을 때만 쓰고, 없으면 **아무 말도 하지 않는다** —
|
|
413
|
+
* 읽을 수 없는 글은 정보가 아니라 잡음이다. 서버에 `en` 이 붙는 날 저절로 살아난다.
|
|
414
|
+
* ★기본값을 `"ko"` 로 둔 것은 자기시험 때문이다. 시험은 `한줄({ ko: ... })` 로 부른다 —
|
|
415
|
+
* 전역 로케일에 기대게 만들면 **영어 PC 에서 시험이 실패한다.**
|
|
416
|
+
*/
|
|
417
|
+
const 한줄 = (s, ㄹ = "ko") => String((ㄹ === "en" ? s.en : s.ko) || "").split("**").join("");
|
|
289
418
|
|
|
290
419
|
/* ── 계단 ② · 구매자 안내 심기 ──────────────────────────────────────────
|
|
291
420
|
★★이게 「계단」의 둘째 칸이다. (2026-08-24 대표 지시)
|
|
@@ -556,7 +685,7 @@ const 끝내기 = (코드) => { process.exitCode = 코드; };
|
|
|
556
685
|
|
|
557
686
|
|
|
558
687
|
async function main() {
|
|
559
|
-
줄(`\n${c.b}${c.cy}
|
|
688
|
+
줄(`\n${c.b}${c.cy} ${G.제목}${c[0]} ${c.d}REBORN LABS${c[0]}\n`);
|
|
560
689
|
|
|
561
690
|
const 탈 = 선행조건();
|
|
562
691
|
if (탈.length) {
|
|
@@ -566,10 +695,10 @@ async function main() {
|
|
|
566
695
|
}
|
|
567
696
|
|
|
568
697
|
if (!토큰) {
|
|
569
|
-
줄(`${c.y}
|
|
570
|
-
줄(`${c.d}
|
|
571
|
-
줄(`${c.d}\n
|
|
572
|
-
줄(`${c.d}
|
|
698
|
+
줄(`${c.y} ${G.토큰없음}${c[0]}`);
|
|
699
|
+
줄(`${c.d} ${G.토큰예}${c[0]}`);
|
|
700
|
+
줄(`${c.d}\n ${G.아직안샀다면(G.착지주소)}${c[0]}`);
|
|
701
|
+
줄(`${c.d} ${G.쓰는법(안내)}${c[0]}\n`);
|
|
573
702
|
return 끝내기(1);
|
|
574
703
|
}
|
|
575
704
|
|
|
@@ -577,8 +706,10 @@ async function main() {
|
|
|
577
706
|
if (!받은것.ok) { 줄(`${c.r} ✗ ${받은것.이유}${c[0]}\n`); return 끝내기(1); }
|
|
578
707
|
|
|
579
708
|
const 스킬들 = 받은것.skills || [];
|
|
580
|
-
if (!스킬들.length) { 줄(`${c.r} ✗
|
|
581
|
-
|
|
709
|
+
if (!스킬들.length) { 줄(`${c.r} ✗ ${G.목록빔}${c[0]}\n`); return 끝내기(1); }
|
|
710
|
+
/* ★`packLabel` 은 서버가 주는 한국어 이름이다. 영어로 도는 실행에서는 우리 이름을 쓴다. */
|
|
711
|
+
const 팩이름 = (말 === "en" && /[가-힣]/.test(String(받은것.packLabel || ""))) ? G.팩기본 : (받은것.packLabel || G.팩기본);
|
|
712
|
+
줄(` ${c.b}${팩이름}${c[0]} ${c.d}${G.종(스킬들.length)}${c[0]}\n`);
|
|
582
713
|
|
|
583
714
|
const 목록 = 설치목록();
|
|
584
715
|
|
|
@@ -590,9 +721,10 @@ async function main() {
|
|
|
590
721
|
const 깔림 = s.skill ? 파일로있나(s.skill).ok : 목록.레포들.has(String(s.repo).toLowerCase());
|
|
591
722
|
if (깔림) 있음++;
|
|
592
723
|
줄(` ${깔림 ? c.g + "●" : c.d + "○"}${c[0]} ${이름(s)}`);
|
|
593
|
-
|
|
724
|
+
const 설명 = 한줄(s, 말);
|
|
725
|
+
줄(` ${c.d}${설명}${설명 ? " · " : ""}${s.repo}${c[0]}`);
|
|
594
726
|
}
|
|
595
|
-
줄(`\n ${
|
|
727
|
+
줄(`\n ${G.깔려있다(있음, 스킬들.length)}\n`);
|
|
596
728
|
return 끝내기(0);
|
|
597
729
|
}
|
|
598
730
|
|
|
@@ -605,25 +737,26 @@ async function main() {
|
|
|
605
737
|
const r = 하나설치(s, 목록.레포들);
|
|
606
738
|
결과.push({ s, r });
|
|
607
739
|
const 색 = { 완료: c.g, 이미: c.d, 반쪽: c.y, 실패: c.r }[r.등급] || c.d;
|
|
608
|
-
줄(`${색}${r.등급}${c[0]} ${c.d}${r.설명}${c[0]}`);
|
|
740
|
+
줄(`${색}${G.등급[r.등급] || r.등급}${c[0]} ${c.d}${r.설명}${c[0]}`);
|
|
609
741
|
}
|
|
610
742
|
|
|
611
743
|
const 셈 = (등급) => 결과.filter((x) => x.r.등급 === 등급).length;
|
|
612
744
|
줄("");
|
|
613
|
-
줄(` ${c.g}
|
|
745
|
+
줄(` ${c.g}${G.셈.완료(셈("완료"))}${c[0]} ${c.d}${G.셈.이미(셈("이미"))}${c[0]}`
|
|
746
|
+
+ ` ${c.y}${G.셈.반쪽(셈("반쪽"))}${c[0]} ${c.r}${G.셈.실패(셈("실패"))}${c[0]}`);
|
|
614
747
|
|
|
615
748
|
/* ★못 깐 것을 숨기지 않는다. 숨기면 고객은 "다 됐다"고 믿고 넘어갔다가
|
|
616
749
|
나중에 "왜 안 되지"를 만난다 — 그게 환불 사유가 된다. */
|
|
617
750
|
const 탈난것 = 결과.filter((x) => x.r.등급 === "실패" || x.r.등급 === "반쪽");
|
|
618
751
|
if (탈난것.length) {
|
|
619
|
-
줄(`\n ${c.y}
|
|
752
|
+
줄(`\n ${c.y}${G.탈난것머리}${c[0]}`);
|
|
620
753
|
for (const { s } of 탈난것) 줄(` ${c.d}·${c[0]} ${이름(s)} ${c.cy}https://github.com/${s.repo}${c[0]}`);
|
|
621
|
-
줄(`\n ${c.d}
|
|
754
|
+
줄(`\n ${c.d}${G.막히면}${c[0]}`);
|
|
622
755
|
}
|
|
623
756
|
|
|
624
|
-
줄(`\n ${c.d}
|
|
625
|
-
줄(`
|
|
626
|
-
줄(`\n ${c.b}
|
|
757
|
+
줄(`\n ${c.d}${G.라이선스1}`);
|
|
758
|
+
줄(` ${G.라이선스2}${c[0]}`);
|
|
759
|
+
줄(`\n ${c.b}${G.재시작}${c[0]}\n`);
|
|
627
760
|
|
|
628
761
|
/* ── 계단 ② ────────────────────────────────────────────────────────
|
|
629
762
|
★**아무것도 못 깔았으면 여기까지 오지 않는다.** 자기 일도 못 한 도구가
|
|
@@ -631,17 +764,23 @@ async function main() {
|
|
|
631
764
|
★끝난 뒤 **딱 한 칸.** 중간에 끼우지 않는다.
|
|
632
765
|
★고지 블록("먼저 밝혀둡니다")을 넣지 않는다 — 전환을 깎는다.
|
|
633
766
|
대신 숨기지 않는 구조로: 심은 파일의 자리와 지우는 법을 그 자리에서 말한다. */
|
|
767
|
+
/* ★★**영어로 도는 실행에는 심지 않는다** (2026-09-20 · 영어권 1차).
|
|
768
|
+
`안내스킬_구매자.md` 는 본문도 `description` 도 한국어다. 스킬은 description 으로 불린다 —
|
|
769
|
+
한국어 description 은 영어권 고객의 클로드가 **영영 부르지 않는다.** 즉 읽지도 못하는 파일이
|
|
770
|
+
남을 뿐이다. 게다가 그 안내가 권하는 클로드킷 자체가 아직 한국어 제품이다.
|
|
771
|
+
파는 것이 없는데 권하는 자리부터 만들면 그게 광고로만 읽힌다.
|
|
772
|
+
→ 영문판 안내 스킬이 생기는 날 이 조건을 풀면 된다. */
|
|
634
773
|
const 깔린수 = 셈("완료") + 셈("이미");
|
|
635
|
-
if (깔린수 > 0) {
|
|
774
|
+
if (깔린수 > 0 && 말 !== "en") {
|
|
636
775
|
const 심음 = 구매자안내심기({
|
|
637
|
-
팩이름:
|
|
776
|
+
팩이름: 팩이름,
|
|
638
777
|
깔린수,
|
|
639
778
|
전체수: 스킬들.length,
|
|
640
779
|
});
|
|
641
780
|
if (심음) {
|
|
642
781
|
줄(` ${c.d}────────────────────────────────────────────${c[0]}`);
|
|
643
|
-
줄(` ${c.d}
|
|
644
|
-
줄(` ~/.claude/skills/reborn-claudekit
|
|
782
|
+
줄(` ${c.d}${G.안내심음1}`);
|
|
783
|
+
줄(` ${G.안내심음2("~/.claude/skills/reborn-claudekit")}${c[0]}\n`);
|
|
645
784
|
}
|
|
646
785
|
}
|
|
647
786
|
|