reborn-token 1.0.0 → 1.0.1
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/cli.mjs +196 -187
- package/package.json +1 -1
- package//354/225/210/353/202/264/354/212/244/355/202/254.md +4 -4
package/cli.mjs
CHANGED
|
@@ -1,187 +1,196 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 토큰 절반 — PDF·워드·엑셀을 **깔끔한 마크다운**으로 바꾼다.
|
|
4
|
-
*
|
|
5
|
-
* npx reborn-token 스킬 설치 (+ 설치 뒤 실제로 돌려 확인)
|
|
6
|
-
* npx reborn-token md <파일...> 변환 → 파일 옆에 .md
|
|
7
|
-
* npx reborn-token md <파일> -o out 나갈 폴더 지정
|
|
8
|
-
* npx reborn-token --check 설치 상태만 본다
|
|
9
|
-
*
|
|
10
|
-
* ★왜 만들었나 — Anthropic 공식 문서(2026-08-24 확인):
|
|
11
|
-
* "The system converts each page of the document into an image.
|
|
12
|
-
* The text from each page is extracted and provided alongside each page's image."
|
|
13
|
-
* 즉 PDF 를 그대로 넣으면 **페이지마다 텍스트와 이미지가 둘 다** 실린다.
|
|
14
|
-
* 마크다운에는 페이지도 이미지도 없다 — 이미지 몫이 통째로 사라진다.
|
|
15
|
-
*
|
|
16
|
-
* ★전부 로컬에서 돈다. 네트워크를 쓰지 않고, API 키가 필요 없다.
|
|
17
|
-
*/
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
|
|
19
|
-
import { homedir } from "node:os";
|
|
20
|
-
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
21
|
-
import { fileURLToPath } from "node:url";
|
|
22
|
-
|
|
23
|
-
const 여기 = dirname(fileURLToPath(import.meta.url));
|
|
24
|
-
const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
|
|
25
|
-
|
|
26
|
-
const 인자 = process.argv.slice(2);
|
|
27
|
-
const 첫 = 인자[0];
|
|
28
|
-
|
|
29
|
-
/* ══ 변환 ═════════════════════════════════════════════════════════════ */
|
|
30
|
-
async function 변환명령(입력) {
|
|
31
|
-
const o = 입력.indexOf("-o");
|
|
32
|
-
let 나갈곳 = null;
|
|
33
|
-
if (o >= 0) { 나갈곳 = 입력[o + 1]; 입력.splice(o, 2); }
|
|
34
|
-
const 파일들 = 입력.filter((a) => !a.startsWith("-"));
|
|
35
|
-
|
|
36
|
-
if (!파일들.length) {
|
|
37
|
-
console.error(`${c.r}✗ 바꿀 파일을 주세요${c[0]} ${c.d}예: npx reborn-token md contract.pdf${c[0]}`);
|
|
38
|
-
process.exit(2);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let 실패 = 0;
|
|
42
|
-
for (const f of 파일들) {
|
|
43
|
-
try { await 하나변환(f, 나갈곳); }
|
|
44
|
-
catch (e) { 실패++; console.error(`${c.r}✗ ${basename(f)}${c[0]} — ${String(e.message || e).slice(0, 160)}`); }
|
|
45
|
-
}
|
|
46
|
-
process.exit(실패 ? 1 : 0);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function 하나변환(경로, 나갈곳) {
|
|
50
|
-
if (!existsSync(경로)) throw new Error("파일이 없습니다");
|
|
51
|
-
const 확장 = extname(경로).toLowerCase();
|
|
52
|
-
const { 요약 } = await import(`file://${join(여기, "lib", "tokens.mjs").replace(/\\/g, "/")}`);
|
|
53
|
-
|
|
54
|
-
let md = "";
|
|
55
|
-
let 쪽수 = 0;
|
|
56
|
-
|
|
57
|
-
if (확장 === ".pdf") {
|
|
58
|
-
const { pdf읽기, 쪽을마크다운 } = await import(`file://${join(여기, "lib", "pdf.mjs").replace(/\\/g, "/")}`);
|
|
59
|
-
const r = await pdf읽기(경로);
|
|
60
|
-
쪽수 = r.쪽수;
|
|
61
|
-
md = r.쪽들.map((줄들) => 쪽을마크다운(줄들)).filter(Boolean).join("\n\n");
|
|
62
|
-
/* ★★스캔본은 **조용히 빈 파일을 내지 않는다.** 그건 「변환됐다」가 아니다.
|
|
63
|
-
여기서 멈추고 왜 안 되는지 말한다 — 빈 .md 를 받아 든 사람은 원인을 못 찾는다. */
|
|
64
|
-
if (md.replace(/\s/g, "").length < 20) {
|
|
65
|
-
throw new Error(`글자가 거의 없습니다(${쪽수}쪽) — 스캔본이거나 이미지로만 된 PDF 입니다. `
|
|
66
|
-
+ `이런 파일은 마크다운으로 바꿀 텍스트 자체가 없어, 그대로 클로드에 넣는 편이 낫습니다.`);
|
|
67
|
-
}
|
|
68
|
-
} else if (확장 === ".docx") {
|
|
69
|
-
const { docx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
70
|
-
md = docx읽기(경로);
|
|
71
|
-
} else if (확장 === ".xlsx") {
|
|
72
|
-
const { xlsx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
73
|
-
md = xlsx읽기(경로);
|
|
74
|
-
} else if (확장 === ".pptx") {
|
|
75
|
-
const { pptx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
76
|
-
md = pptx읽기(경로);
|
|
77
|
-
} else {
|
|
78
|
-
throw new Error(`아직 못 바꾸는 형식입니다(${확장 || "확장자 없음"}) — pdf · docx · xlsx · pptx 를 지원합니다`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!md.trim()) throw new Error("가져올 글자가 없습니다");
|
|
82
|
-
|
|
83
|
-
const 폴더 = 나갈곳 ? resolve(나갈곳) : dirname(resolve(경로));
|
|
84
|
-
mkdirSync(폴더, { recursive: true });
|
|
85
|
-
const 나감 = join(폴더, basename(경로, extname(경로)) + ".md");
|
|
86
|
-
writeFileSync(나감, md.replace(/\n{3,}/g, "\n\n").trim() + "\n", "utf8");
|
|
87
|
-
|
|
88
|
-
console.log(`\n${c.g}✓${c[0]} ${basename(경로)} → ${c.b}${basename(나감)}${c[0]}`);
|
|
89
|
-
if (쪽수) console.log(요약(쪽수, md));
|
|
90
|
-
else console.log(` ${c.d}글자 ${md.length.toLocaleString("ko-KR")}자 · 페이지 이미지 0${c[0]}`);
|
|
91
|
-
console.log(` ${c.d}${나감}${c[0]}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/* ══ 설치 ═════════════════════════════════════════════════════════════ */
|
|
95
|
-
const 스킬이름 = "pdf-to-md";
|
|
96
|
-
const 대상 = join(homedir(), ".claude", "skills", 스킬이름);
|
|
97
|
-
|
|
98
|
-
function 파일목록(뿌리) {
|
|
99
|
-
const 나온다 = [];
|
|
100
|
-
const 걷기 = (d) => {
|
|
101
|
-
for (const 이름 of readdirSync(d)) {
|
|
102
|
-
const p = join(d, 이름);
|
|
103
|
-
if (statSync(p).isDirectory()) 걷기(p);
|
|
104
|
-
else 나온다.push(p);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
걷기(뿌리);
|
|
108
|
-
return 나온다;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** ★파일이 있다는 것으로 통과시키지 않는다. **실제로 한 장을 바꿔 본다.** */
|
|
112
|
-
async function 진짜도나() {
|
|
113
|
-
const md = join(대상, "SKILL.md");
|
|
114
|
-
if (!existsSync(md)) return { 됨: false, 이유: "SKILL.md 가 없습니다" };
|
|
115
|
-
if (!/^---[\s\S]*?\bname:\s*pdf-to-md\b/m.test(readFileSync(md, "utf8"))) {
|
|
116
|
-
return { 됨: false, 이유: "SKILL.md 의 머리말이 깨졌습니다(name 을 못 읽습니다)" };
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
const { docx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
120
|
-
const { 텍스트토큰추정 } = await import(`file://${join(여기, "lib", "tokens.mjs").replace(/\\/g, "/")}`);
|
|
121
|
-
if (typeof docx읽기 !== "function") return { 됨: false, 이유: "office 변환기를 못 불렀습니다" };
|
|
122
|
-
if (텍스트토큰추정("가나다 abcd") <= 0) return { 됨: false, 이유: "토큰 추정기가 0 을 냅니다" };
|
|
123
|
-
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
124
|
-
if (!pdfjs?.getDocument) return { 됨: false, 이유: "pdfjs 를 못 불렀습니다" };
|
|
125
|
-
return { 됨: true };
|
|
126
|
-
} catch (e) {
|
|
127
|
-
return { 됨: false, 이유: `변환기를 부르다 걸렸습니다 — ${String(e.message || e).slice(0, 120)}` };
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function 설치확인만() {
|
|
132
|
-
머리말();
|
|
133
|
-
const r = await 진짜도나();
|
|
134
|
-
if (r.됨) console.log(`${c.g}✓ 설치되어 있고 변환기가 실제로 뜹니다${c[0]} ${c.d}(${대상})${c[0]}\n`);
|
|
135
|
-
else console.log(`${c.y}· 아직 없습니다${c[0]} ${c.d}— ${r.이유}${c[0]}\n`);
|
|
136
|
-
process.exit(r.됨 ? 0 : 1);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function 머리말() {
|
|
140
|
-
console.log(`\n${c.b}토큰 절반${c[0]} ${c.d}· REBORN LABS · MIT${c[0]}`);
|
|
141
|
-
console.log(`${c.d}PDF·워드·엑셀을 깔끔한 마크다운으로 바꿔, 페이지 이미지 토큰을 없앱니다.${c[0]}\n`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function 설치() {
|
|
145
|
-
머리말();
|
|
146
|
-
const 원본 = join(여기, "skill");
|
|
147
|
-
if (!existsSync(원본)) {
|
|
148
|
-
console.error(`${c.r}✗ 패키지 안에 skill/ 폴더가 없습니다${c[0]} — 배포가 잘못된 것입니다.`);
|
|
149
|
-
process.exit(2);
|
|
150
|
-
}
|
|
151
|
-
let 쓴수 = 0;
|
|
152
|
-
for (const src of 파일목록(원본)) {
|
|
153
|
-
const 상대 = relative(원본, src);
|
|
154
|
-
const dst = join(대상, 상대);
|
|
155
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
156
|
-
writeFileSync(dst, readFileSync(src));
|
|
157
|
-
쓴수++;
|
|
158
|
-
console.log(` ${c.d}·${c[0]} ${상대}`);
|
|
159
|
-
}
|
|
160
|
-
console.log(`\n${c.d}${쓴수}개 파일 → ${대상}${c[0]}`);
|
|
161
|
-
console.log(`${c.d}변환기를 실제로 불러 확인하는 중…${c[0]}`);
|
|
162
|
-
|
|
163
|
-
const r = await 진짜도나();
|
|
164
|
-
if (!r.됨) {
|
|
165
|
-
console.error(`\n${c.r}✗ 깔았는데 확인에서 걸렸습니다${c[0]} — ${r.이유}\n`);
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
console.log(`\n${c.g}${c.b}✓ 설치 완료${c[0]} ${c.d}— 변환기가 실제로 떴습니다.${c[0]}`);
|
|
170
|
-
console.log(`\n${c.y}★ 클로드 코드를 껐다 켜야 스킬이 잡힙니다.${c[0]}`);
|
|
171
|
-
console.log(`\n${c.b}쓰는 법${c[0]}`);
|
|
172
|
-
console.log(` 클로드에게 그냥 말하세요 — ${c.cy}"이 PDF 마크다운으로 바꿔줘"${c[0]}`);
|
|
173
|
-
console.log(` 터미널에서 바로: ${c.cy}npx reborn-token md contract.pdf${c[0]}`);
|
|
174
|
-
console.log(` ${c.d}pdf · docx · xlsx · pptx → 제목과 표를 살린 .md${c[0]}\n`);
|
|
175
|
-
console.log(`${c.d}설명 · https://rebornlabs.kr/token?utm_source=installer&utm_medium=cli&utm_campaign=token${c[0]}`);
|
|
176
|
-
/* ★무료 배포물 안에 유료 제품 안내를 둔다. 숨기지 않는다. */
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 토큰 절반 — PDF·워드·엑셀을 **깔끔한 마크다운**으로 바꾼다.
|
|
4
|
+
*
|
|
5
|
+
* npx reborn-token 스킬 설치 (+ 설치 뒤 실제로 돌려 확인)
|
|
6
|
+
* npx reborn-token md <파일...> 변환 → 파일 옆에 .md
|
|
7
|
+
* npx reborn-token md <파일> -o out 나갈 폴더 지정
|
|
8
|
+
* npx reborn-token --check 설치 상태만 본다
|
|
9
|
+
*
|
|
10
|
+
* ★왜 만들었나 — Anthropic 공식 문서(2026-08-24 확인):
|
|
11
|
+
* "The system converts each page of the document into an image.
|
|
12
|
+
* The text from each page is extracted and provided alongside each page's image."
|
|
13
|
+
* 즉 PDF 를 그대로 넣으면 **페이지마다 텍스트와 이미지가 둘 다** 실린다.
|
|
14
|
+
* 마크다운에는 페이지도 이미지도 없다 — 이미지 몫이 통째로 사라진다.
|
|
15
|
+
*
|
|
16
|
+
* ★전부 로컬에서 돈다. 네트워크를 쓰지 않고, API 키가 필요 없다.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
|
|
23
|
+
const 여기 = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
|
|
25
|
+
|
|
26
|
+
const 인자 = process.argv.slice(2);
|
|
27
|
+
const 첫 = 인자[0];
|
|
28
|
+
|
|
29
|
+
/* ══ 변환 ═════════════════════════════════════════════════════════════ */
|
|
30
|
+
async function 변환명령(입력) {
|
|
31
|
+
const o = 입력.indexOf("-o");
|
|
32
|
+
let 나갈곳 = null;
|
|
33
|
+
if (o >= 0) { 나갈곳 = 입력[o + 1]; 입력.splice(o, 2); }
|
|
34
|
+
const 파일들 = 입력.filter((a) => !a.startsWith("-"));
|
|
35
|
+
|
|
36
|
+
if (!파일들.length) {
|
|
37
|
+
console.error(`${c.r}✗ 바꿀 파일을 주세요${c[0]} ${c.d}예: npx reborn-token md contract.pdf${c[0]}`);
|
|
38
|
+
process.exit(2);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let 실패 = 0;
|
|
42
|
+
for (const f of 파일들) {
|
|
43
|
+
try { await 하나변환(f, 나갈곳); }
|
|
44
|
+
catch (e) { 실패++; console.error(`${c.r}✗ ${basename(f)}${c[0]} — ${String(e.message || e).slice(0, 160)}`); }
|
|
45
|
+
}
|
|
46
|
+
process.exit(실패 ? 1 : 0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function 하나변환(경로, 나갈곳) {
|
|
50
|
+
if (!existsSync(경로)) throw new Error("파일이 없습니다");
|
|
51
|
+
const 확장 = extname(경로).toLowerCase();
|
|
52
|
+
const { 요약 } = await import(`file://${join(여기, "lib", "tokens.mjs").replace(/\\/g, "/")}`);
|
|
53
|
+
|
|
54
|
+
let md = "";
|
|
55
|
+
let 쪽수 = 0;
|
|
56
|
+
|
|
57
|
+
if (확장 === ".pdf") {
|
|
58
|
+
const { pdf읽기, 쪽을마크다운 } = await import(`file://${join(여기, "lib", "pdf.mjs").replace(/\\/g, "/")}`);
|
|
59
|
+
const r = await pdf읽기(경로);
|
|
60
|
+
쪽수 = r.쪽수;
|
|
61
|
+
md = r.쪽들.map((줄들) => 쪽을마크다운(줄들)).filter(Boolean).join("\n\n");
|
|
62
|
+
/* ★★스캔본은 **조용히 빈 파일을 내지 않는다.** 그건 「변환됐다」가 아니다.
|
|
63
|
+
여기서 멈추고 왜 안 되는지 말한다 — 빈 .md 를 받아 든 사람은 원인을 못 찾는다. */
|
|
64
|
+
if (md.replace(/\s/g, "").length < 20) {
|
|
65
|
+
throw new Error(`글자가 거의 없습니다(${쪽수}쪽) — 스캔본이거나 이미지로만 된 PDF 입니다. `
|
|
66
|
+
+ `이런 파일은 마크다운으로 바꿀 텍스트 자체가 없어, 그대로 클로드에 넣는 편이 낫습니다.`);
|
|
67
|
+
}
|
|
68
|
+
} else if (확장 === ".docx") {
|
|
69
|
+
const { docx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
70
|
+
md = docx읽기(경로);
|
|
71
|
+
} else if (확장 === ".xlsx") {
|
|
72
|
+
const { xlsx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
73
|
+
md = xlsx읽기(경로);
|
|
74
|
+
} else if (확장 === ".pptx") {
|
|
75
|
+
const { pptx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
76
|
+
md = pptx읽기(경로);
|
|
77
|
+
} else {
|
|
78
|
+
throw new Error(`아직 못 바꾸는 형식입니다(${확장 || "확장자 없음"}) — pdf · docx · xlsx · pptx 를 지원합니다`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!md.trim()) throw new Error("가져올 글자가 없습니다");
|
|
82
|
+
|
|
83
|
+
const 폴더 = 나갈곳 ? resolve(나갈곳) : dirname(resolve(경로));
|
|
84
|
+
mkdirSync(폴더, { recursive: true });
|
|
85
|
+
const 나감 = join(폴더, basename(경로, extname(경로)) + ".md");
|
|
86
|
+
writeFileSync(나감, md.replace(/\n{3,}/g, "\n\n").trim() + "\n", "utf8");
|
|
87
|
+
|
|
88
|
+
console.log(`\n${c.g}✓${c[0]} ${basename(경로)} → ${c.b}${basename(나감)}${c[0]}`);
|
|
89
|
+
if (쪽수) console.log(요약(쪽수, md));
|
|
90
|
+
else console.log(` ${c.d}글자 ${md.length.toLocaleString("ko-KR")}자 · 페이지 이미지 0${c[0]}`);
|
|
91
|
+
console.log(` ${c.d}${나감}${c[0]}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ══ 설치 ═════════════════════════════════════════════════════════════ */
|
|
95
|
+
const 스킬이름 = "pdf-to-md";
|
|
96
|
+
const 대상 = join(homedir(), ".claude", "skills", 스킬이름);
|
|
97
|
+
|
|
98
|
+
function 파일목록(뿌리) {
|
|
99
|
+
const 나온다 = [];
|
|
100
|
+
const 걷기 = (d) => {
|
|
101
|
+
for (const 이름 of readdirSync(d)) {
|
|
102
|
+
const p = join(d, 이름);
|
|
103
|
+
if (statSync(p).isDirectory()) 걷기(p);
|
|
104
|
+
else 나온다.push(p);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
걷기(뿌리);
|
|
108
|
+
return 나온다;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** ★파일이 있다는 것으로 통과시키지 않는다. **실제로 한 장을 바꿔 본다.** */
|
|
112
|
+
async function 진짜도나() {
|
|
113
|
+
const md = join(대상, "SKILL.md");
|
|
114
|
+
if (!existsSync(md)) return { 됨: false, 이유: "SKILL.md 가 없습니다" };
|
|
115
|
+
if (!/^---[\s\S]*?\bname:\s*pdf-to-md\b/m.test(readFileSync(md, "utf8"))) {
|
|
116
|
+
return { 됨: false, 이유: "SKILL.md 의 머리말이 깨졌습니다(name 을 못 읽습니다)" };
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const { docx읽기 } = await import(`file://${join(여기, "lib", "office.mjs").replace(/\\/g, "/")}`);
|
|
120
|
+
const { 텍스트토큰추정 } = await import(`file://${join(여기, "lib", "tokens.mjs").replace(/\\/g, "/")}`);
|
|
121
|
+
if (typeof docx읽기 !== "function") return { 됨: false, 이유: "office 변환기를 못 불렀습니다" };
|
|
122
|
+
if (텍스트토큰추정("가나다 abcd") <= 0) return { 됨: false, 이유: "토큰 추정기가 0 을 냅니다" };
|
|
123
|
+
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
124
|
+
if (!pdfjs?.getDocument) return { 됨: false, 이유: "pdfjs 를 못 불렀습니다" };
|
|
125
|
+
return { 됨: true };
|
|
126
|
+
} catch (e) {
|
|
127
|
+
return { 됨: false, 이유: `변환기를 부르다 걸렸습니다 — ${String(e.message || e).slice(0, 120)}` };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function 설치확인만() {
|
|
132
|
+
머리말();
|
|
133
|
+
const r = await 진짜도나();
|
|
134
|
+
if (r.됨) console.log(`${c.g}✓ 설치되어 있고 변환기가 실제로 뜹니다${c[0]} ${c.d}(${대상})${c[0]}\n`);
|
|
135
|
+
else console.log(`${c.y}· 아직 없습니다${c[0]} ${c.d}— ${r.이유}${c[0]}\n`);
|
|
136
|
+
process.exit(r.됨 ? 0 : 1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function 머리말() {
|
|
140
|
+
console.log(`\n${c.b}토큰 절반${c[0]} ${c.d}· REBORN LABS · MIT${c[0]}`);
|
|
141
|
+
console.log(`${c.d}PDF·워드·엑셀을 깔끔한 마크다운으로 바꿔, 페이지 이미지 토큰을 없앱니다.${c[0]}\n`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function 설치() {
|
|
145
|
+
머리말();
|
|
146
|
+
const 원본 = join(여기, "skill");
|
|
147
|
+
if (!existsSync(원본)) {
|
|
148
|
+
console.error(`${c.r}✗ 패키지 안에 skill/ 폴더가 없습니다${c[0]} — 배포가 잘못된 것입니다.`);
|
|
149
|
+
process.exit(2);
|
|
150
|
+
}
|
|
151
|
+
let 쓴수 = 0;
|
|
152
|
+
for (const src of 파일목록(원본)) {
|
|
153
|
+
const 상대 = relative(원본, src);
|
|
154
|
+
const dst = join(대상, 상대);
|
|
155
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
156
|
+
writeFileSync(dst, readFileSync(src));
|
|
157
|
+
쓴수++;
|
|
158
|
+
console.log(` ${c.d}·${c[0]} ${상대}`);
|
|
159
|
+
}
|
|
160
|
+
console.log(`\n${c.d}${쓴수}개 파일 → ${대상}${c[0]}`);
|
|
161
|
+
console.log(`${c.d}변환기를 실제로 불러 확인하는 중…${c[0]}`);
|
|
162
|
+
|
|
163
|
+
const r = await 진짜도나();
|
|
164
|
+
if (!r.됨) {
|
|
165
|
+
console.error(`\n${c.r}✗ 깔았는데 확인에서 걸렸습니다${c[0]} — ${r.이유}\n`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`\n${c.g}${c.b}✓ 설치 완료${c[0]} ${c.d}— 변환기가 실제로 떴습니다.${c[0]}`);
|
|
170
|
+
console.log(`\n${c.y}★ 클로드 코드를 껐다 켜야 스킬이 잡힙니다.${c[0]}`);
|
|
171
|
+
console.log(`\n${c.b}쓰는 법${c[0]}`);
|
|
172
|
+
console.log(` 클로드에게 그냥 말하세요 — ${c.cy}"이 PDF 마크다운으로 바꿔줘"${c[0]}`);
|
|
173
|
+
console.log(` 터미널에서 바로: ${c.cy}npx reborn-token md contract.pdf${c[0]}`);
|
|
174
|
+
console.log(` ${c.d}pdf · docx · xlsx · pptx → 제목과 표를 살린 .md${c[0]}\n`);
|
|
175
|
+
console.log(`${c.d}설명 · https://rebornlabs.kr/token?utm_source=installer&utm_medium=cli&utm_campaign=token${c[0]}`);
|
|
176
|
+
/* ★무료 배포물 안에 유료 제품 안내를 둔다. 숨기지 않는다. */
|
|
177
|
+
/* ★가리키는 곳을 158,000원 클로드킷 → 8,900원 스킬팩으로 내렸다 (2026-08-24).
|
|
178
|
+
무료 다음 칸이 곧바로 158,000원이라 아무도 못 건넜다(판매 페이지까지 간 10명 중 0명).
|
|
179
|
+
자비스는 스킬팩을 산 사람에게 민다 — 그래야 계단이 생긴다.
|
|
180
|
+
★문안 정본은 skillpack/tail_copy.mjs. */
|
|
181
|
+
console.log(`${c.d}방금 들어가는 토큰을 줄였습니다. 나가는 토큰과 헛일까지 줄이는 묶음이 따로 있습니다.${c[0]}`);
|
|
182
|
+
console.log(`
|
|
183
|
+
${c.b}리본랩스 스킬팩 · 토큰절약형${c[0]}${c.d} — 12종 3,900원 — 전부 48종은 8,900원${c[0]}`);
|
|
184
|
+
console.log(`${c.cy}https://rebornlabs.kr/skillpack?utm_source=installer&utm_medium=cli&utm_campaign=token${c[0]}`);
|
|
185
|
+
console.log(`${c.d}목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.${c[0]}
|
|
186
|
+
`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ══ 실행 ═════════════════════════════════════════════════════════════
|
|
190
|
+
★★맨 위에서 부르면 안 된다. 최상단 await 는 **선언 순서대로** 돈다 —
|
|
191
|
+
아래에 `const` 로 둔 값(대상 · 스킬이름)이 아직 초기화되기 전이라
|
|
192
|
+
`Cannot access '대상' before initialization` 으로 죽는다.
|
|
193
|
+
2026-08-24 에 실제로 그랬다. 실행은 파일 끝에서 한다. */
|
|
194
|
+
if (첫 === "md" || 첫 === "convert" || 첫 === "변환") await 변환명령(인자.slice(1));
|
|
195
|
+
else if (인자.includes("--check")) await 설치확인만();
|
|
196
|
+
else await 설치();
|
package/package.json
CHANGED
|
@@ -41,10 +41,10 @@ Anthropic 공식 문서가 이렇게 적어 두었습니다 —
|
|
|
41
41
|
문서를 아껴 넣어도 **매일 알아서 올라가지는 않습니다.** 그건 파이프라인이 하는 일입니다.
|
|
42
42
|
|
|
43
43
|
## 그 다음이 필요하면
|
|
44
|
+
리본랩스 **스킬팩 · 토큰절약형** — 나가는 토큰과 헛일까지 줄이는 12종(3,900원).
|
|
45
|
+
전부 48종은 8,900원입니다.
|
|
46
|
+
**목록과 직접 까는 명령이 같은 주소에 전부 공개돼 있습니다** — 한 푼도 안 내고 쓰실 수 있습니다.
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
대본부터 이미지, 업로드, 실패 복구까지 한 덩어리로 돕니다.
|
|
47
|
-
|
|
48
|
-
https://rebornlabs.kr/claudekit?utm_source=installer&utm_medium=cli&utm_campaign=token
|
|
48
|
+
https://rebornlabs.kr/skillpack?utm_source=installer&utm_medium=cli&utm_campaign=token
|
|
49
49
|
|
|
50
50
|
> 광고입니다. 다만 `pdf-to-md` 는 그것과 무관하게 그냥 쓰셔도 됩니다 — MIT 이고 완전 무료입니다.
|