reborn-scrapegraph 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,71 @@
1
+ # reborn-scrapegraph
2
+
3
+ 영상에서 소개한 **ScrapeGraphAI** 를 실제로 쓸 수 있게 만드는 안내 스킬입니다.
4
+
5
+ ```bash
6
+ npx --yes reborn-scrapegraph
7
+ ```
8
+
9
+ ## 이게 무엇을 하나
10
+
11
+ `~/.claude/skills/scrapegraph-guide/` 에 안내 스킬 하나를 심습니다. 그게 전부입니다.
12
+
13
+ **ScrapeGraphAI 자체는 저희가 깔지 않습니다.** `pip install scrapegraphai` 한 줄이면
14
+ 누구나 깝니다 — 그걸 대신 눌러 주는 건 아무 값도 안 만듭니다.
15
+
16
+ ## 그럼 무엇을 파나
17
+
18
+ 실제로 막히는 자리는 설치가 아니라 그 다음입니다.
19
+
20
+ | 막히는 자리 | 이 패키지가 하는 일 |
21
+ |---|---|
22
+ | 「무료」인 줄 알고 깔았는데 LLM 키를 넣으래요 | **맨 앞에서** 무료의 범위를 말합니다 |
23
+ | 에러도 안 나는데 결과가 비어 있어요 | `playwright install` 이 왔는지 **실제로 잽니다** |
24
+ | 파이썬이 여러 개라 깐 쪽과 도는 쪽이 달라요 | 버전이 아니라 **`import` 로** 고릅니다 |
25
+
26
+ ```bash
27
+ npx --yes reborn-scrapegraph --check # 깔지 않고 이 컴퓨터만 잽니다
28
+ ```
29
+
30
+ ## 무료의 범위
31
+
32
+ ScrapeGraphAI 는 두 가지 물건입니다.
33
+
34
+ | | 오픈소스 라이브러리 | 매니지드 클라우드 API |
35
+ |---|---|---|
36
+ | 무엇 | `scrapegraphai` — 내가 돌립니다 | `scrapegraph-py` / `scrapegraph-js` |
37
+ | 값 | **MIT · 무료** | **별개의 유료 서비스** |
38
+ | LLM | **내 키** 또는 **Ollama 로컬** | 저쪽이 관리 |
39
+
40
+ **「무료」는 왼쪽 칸 이야기입니다.** 라이브러리는 정말 무료이고, Ollama 로 로컬 모델을 쓰면
41
+ 한 푼도 안 듭니다 — 대신 내 PC 가 돕니다.
42
+
43
+ ## 안 깔고 직접 하실 수도 있습니다
44
+
45
+ ```bash
46
+ pip install scrapegraphai
47
+ playwright install
48
+ ```
49
+
50
+ 스킬 파일은 `skill/SKILL.md` 에 그대로 있습니다. 열어 보시고 손으로 옮기셔도 됩니다.
51
+
52
+ ## 명령
53
+
54
+ | | |
55
+ |---|---|
56
+ | `npx --yes reborn-scrapegraph` | 안내 스킬을 깝니다 |
57
+ | `--check` | 깔지 않고 이 컴퓨터 상태만 잽니다 |
58
+ | `--dry-run` | 무엇을 할지 보여주기만 합니다 |
59
+ | `--local` | 홈이 아니라 현재 폴더의 `.claude/skills` 에 깝니다 |
60
+ | `--self-test` | 자기시험 (네트워크 안 씁니다) |
61
+
62
+ ## 밝혀 둡니다
63
+
64
+ - **ScrapeGraphAI 는 ScrapeGraphAI 팀이 만든 별개의 MIT 오픈소스**이고 저희와 아무 관계가 없습니다.
65
+ 이 패키지에는 그쪽 코드가 **한 줄도 들어 있지 않습니다.**
66
+ - 저희가 심는 건 **저희가 쓴 안내 문서** 하나뿐입니다.
67
+ - 긁어도 되는지는 대상 사이트의 이용약관·`robots.txt`·개인정보 규정에 달려 있습니다.
68
+ 저희가 법률 판단을 대신 내리지 않습니다.
69
+ - 수치(별·포크·라이선스)는 `skill/scrapegraph.json` 에 **확인일과 확인방법**을 달아 두었습니다.
70
+
71
+ MIT · REBORN LABS
package/cli.mjs ADDED
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scrapegraph-guide 설치기 — **영상이 말한 그 도구를 실제로 쓸 수 있게 만든다.**
4
+ *
5
+ * npx --yes reborn-scrapegraph 안내 스킬을 깐다 (이게 전부다)
6
+ * npx --yes reborn-scrapegraph --check ★깔지 않고 **이 컴퓨터를 잰다**
7
+ * npx --yes reborn-scrapegraph --dry-run 무엇을 할지 보여주기만 한다
8
+ * npx --yes reborn-scrapegraph --local 홈(~/.claude/skills)이 아니라 현재 폴더에 깐다
9
+ * npx --yes reborn-scrapegraph --self-test 자기시험 (네트워크 안 씀)
10
+ *
11
+ * ★★왜 라이브러리를 우리가 안 깔고 **안내 스킬**을 까나
12
+ * ScrapeGraphAI 는 MIT 오픈소스 파이썬 라이브러리다. `pip install scrapegraphai` 한 줄이면
13
+ * 누구나 깐다 — 그걸 우리가 대신 눌러 주는 건 아무 값도 안 만든다.
14
+ * 실제로 막히는 자리는 설치가 아니라 **그 다음**이다:
15
+ * ① 「무료」인 줄 알고 깔았는데 LLM 키를 넣으라고 한다
16
+ * ② `playwright install` 을 안 해서 **에러 없이 빈 결과**가 돌아온다
17
+ * ③ 파이썬이 여러 개라 깐 쪽과 도는 쪽이 다르다
18
+ * 우리가 파는 건 코드가 아니라 **그 셋을 미리 말해 주는 것**과 **실제로 재는 것**이다.
19
+ *
20
+ * ★★--check 가 이 설치기의 존재 이유다
21
+ * 말로 「되실 겁니다」 하지 않는다. `import` 를 실제로 돌려서 판정한다.
22
+ * ★파이썬은 **버전 문자열이 아니라 import 로 고른다** [[python-interpreter-must-be-probed]] —
23
+ * `python --version` 이 3.12 를 답해도 라이브러리는 3.11 에 깔려 있을 수 있다.
24
+ *
25
+ * ★★깔았다고 「됐다」고 하지 않는다.
26
+ * 복사는 조용히 실패한다(권한·경로·한글 홈디렉터리). 쓴 뒤 **다시 읽어 본다**
27
+ * [[kit-update-selfcheck]].
28
+ */
29
+ import { execFileSync } from "node:child_process";
30
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
31
+ import { homedir } from "node:os";
32
+ import { dirname, join } from "node:path";
33
+ import { fileURLToPath } from "node:url";
34
+ import { 링크, TAIL, 마지막 } from "./tail.mjs";
35
+
36
+ const HOME = homedir();
37
+ const 여기 = dirname(fileURLToPath(import.meta.url));
38
+ const 인자 = process.argv.slice(2);
39
+ const 확인만 = 인자.includes("--check");
40
+ const 미리보기 = 인자.includes("--dry-run");
41
+ const 여기에 = 인자.includes("--local");
42
+ const 자기시험모드 = 인자.includes("--self-test");
43
+
44
+ const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
45
+ const 굵게 = (t) => String(t).replace(/\*\*(.+?)\*\*/g, `${c[0]}${c.b}$1${c[0]}${c.d}`);
46
+
47
+ const 퍼널 = "scrape";
48
+ const 스킬이름 = "scrapegraph-guide";
49
+ const 원본 = join(여기, "skill");
50
+ const 대상뿌리 = 여기에 ? join(process.cwd(), ".claude", "skills") : join(HOME, ".claude", "skills");
51
+ const 대상 = join(대상뿌리, 스킬이름);
52
+
53
+ /* ══ 원장 ═══════════════════════════════════════════════════════ */
54
+
55
+ function 원장읽기() {
56
+ const p = join(원본, "scrapegraph.json");
57
+ if (!existsSync(p)) return { 됨: false, 이유: `원장이 없다 — ${p}` };
58
+ try { return { 됨: true, d: JSON.parse(readFileSync(p, "utf8")) }; }
59
+ catch (e) { return { 됨: false, 이유: `원장을 못 읽는다 — ${e.message}` }; }
60
+ }
61
+
62
+ /** ★조건 없이 단정하는 말. 홍보문에 섞이면 잡는다. [[kmong-no-unbounded-guarantee]] */
63
+ const 금칙 = [/완전\s*무료/, /무제한/, /평생/, /100\s*%\s*보장/, /누구나\s*바로/, /무조건/,
64
+ /아무\s*사이트나/, /차단\s*없이/, /합법/];
65
+
66
+ function 원장검사(d) {
67
+ const 문제 = [];
68
+ const 있나 = (v) => v !== undefined && v !== null && v !== "";
69
+
70
+ if (!있나(d?.확인일)) 문제.push("확인일이 없다 — 언제 잰 값인지 모르면 수치는 못 쓴다");
71
+ if (!있나(d?.확인방법)) 문제.push("확인방법이 없다 — 어디서 셌는지 없는 숫자는 지어낸 것과 구분이 안 된다");
72
+ for (const k of ["별", "포크", "관심"]) {
73
+ if (!Number.isFinite(d?.숫자?.[k])) 문제.push(`숫자.${k} 가 없다`);
74
+ }
75
+ if (d?.스킬?.라이선스 !== "MIT") 문제.push("라이선스가 MIT 가 아니다");
76
+ if (!/pip install/.test(d?.스킬?.설치 || "")) 문제.push("설치 명령이 없다");
77
+ if (!/playwright/.test(d?.스킬?.브라우저 || ""))
78
+ 문제.push("브라우저 설치 줄이 없다 — 이게 빠지면 에러 없이 빈 결과가 나간다");
79
+
80
+ /* ★★이 킷의 존재 이유 — 「무료의 범위」가 비면 통과시키지 않는다.
81
+ 이게 없으면 우리는 참고본과 똑같은 말만 하는 패키지가 된다. */
82
+ const 범위 = d?.["★무료의범위"];
83
+ if (!범위) 문제.push("★무료의범위 칸이 없다 — 이 패키지가 존재할 이유가 사라진다");
84
+ else {
85
+ if (!/무료/.test(범위.라이브러리 || "")) 문제.push("★무료의범위.라이브러리 가 무료라고 하지 않는다");
86
+ if (!/유료|별개/.test(범위.클라우드API || ""))
87
+ 문제.push("★무료의범위.클라우드API 가 유료임을 말하지 않는다 — 그러면 범위를 안 나눈 것이다");
88
+ if (!Array.isArray(범위.비교표) || 범위.비교표.length < 3)
89
+ 문제.push("★무료의범위.비교표 가 3줄 미만이다");
90
+ }
91
+
92
+ const 전문 = JSON.stringify(d);
93
+ for (const re of 금칙) if (re.test(전문)) 문제.push(`원장에 조건 없는 단정이 있다: ${re.source}`);
94
+ return 문제;
95
+ }
96
+
97
+ /* ══ 이 컴퓨터를 잰다 ══════════════════════════════════════════════
98
+ ★말로 판정하지 않는다. 실제로 돌린다. 못 돌리면 「모른다」고 한다 — 「된다」고 하지 않는다. */
99
+
100
+ /** 파이썬 후보. ★버전 문자열이 아니라 **import 결과**로 고른다. */
101
+ const 파이썬후보 = process.platform === "win32"
102
+ ? [["py", ["-3.12"]], ["py", ["-3.11"]], ["py", ["-3"]], ["python", []], ["python3", []]]
103
+ : [["python3", []], ["python", []]];
104
+
105
+ function 파이썬돌리기(cmd, pre, code) {
106
+ try {
107
+ const out = execFileSync(cmd, [...pre, "-c", code], {
108
+ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 20000,
109
+ });
110
+ return { 됨: true, 답: String(out).trim() };
111
+ } catch (e) {
112
+ return { 됨: false, 답: String(e.stderr || e.message || "").trim().split("\n").pop() };
113
+ }
114
+ }
115
+
116
+ function 환경재기() {
117
+ const 결과 = { 파이썬: null, 라이브러리: null, 플레이라이트: null, 브라우저: null, 키: [], 올라마: false };
118
+
119
+ for (const [cmd, pre] of 파이썬후보) {
120
+ const v = 파이썬돌리기(cmd, pre, "import sys;print(sys.version.split()[0])");
121
+ if (!v.됨) continue;
122
+ const lib = 파이썬돌리기(cmd, pre, "import scrapegraphai as s;print(getattr(s,'__version__','?'))");
123
+ /* ★★import 되는 쪽을 우선한다. 안 되면 첫 번째로 도는 인터프리터를 기록해 두고 계속 본다. */
124
+ if (!결과.파이썬 || lib.됨) {
125
+ 결과.파이썬 = { 명령: [cmd, ...pre].join(" "), 버전: v.답 };
126
+ 결과.라이브러리 = lib.됨 ? lib.답 : null;
127
+ const pw = 파이썬돌리기(cmd, pre, "import playwright;print('ok')");
128
+ 결과.플레이라이트 = pw.됨;
129
+ if (pw.됨) {
130
+ /* ★「깔렸나」와 「브라우저를 받았나」는 다른 질문이다.
131
+ playwright 모듈만 있고 브라우저 바이너리가 없으면 **에러 없이 빈 결과**가 나온다. */
132
+ const br = 파이썬돌리기(cmd, pre,
133
+ "from playwright.sync_api import sync_playwright\n" +
134
+ "p=sync_playwright().start()\n" +
135
+ "print(p.chromium.executable_path)\n" +
136
+ "p.stop()");
137
+ 결과.브라우저 = br.됨 && !!br.답 && existsSync(br.답.split("\n").pop().trim());
138
+ }
139
+ }
140
+ if (lib.됨) break; // 라이브러리가 도는 인터프리터를 찾았으면 그게 정본이다
141
+ }
142
+
143
+ for (const k of ["OPENAI_API_KEY", "GROQ_API_KEY", "GOOGLE_API_KEY", "GEMINI_API_KEY", "AZURE_OPENAI_API_KEY"]) {
144
+ if (process.env[k]) 결과.키.push(k);
145
+ }
146
+ /* Ollama 는 떠 있어야 의미가 있다 — 깔렸는지가 아니라 **응답하는지**를 본다. */
147
+ try {
148
+ execFileSync(process.execPath, ["-e",
149
+ "fetch('http://localhost:11434/api/tags').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))"],
150
+ { stdio: "ignore", timeout: 4000 });
151
+ 결과.올라마 = true;
152
+ } catch { /* 안 떠 있다. 그게 답이다 */ }
153
+
154
+ return 결과;
155
+ }
156
+
157
+ function 환경보고(E, D) {
158
+ const 표 = (ok, 좋음, 나쁨) => `${ok ? c.g + "✓" : c.y + "…"} ${c[0]}${ok ? 좋음 : 나쁨}`;
159
+ console.log(`\n${c.b}이 컴퓨터를 실제로 재 봤습니다${c[0]}`);
160
+ console.log(`${c.d}${"─".repeat(58)}${c[0]}`);
161
+
162
+ if (!E.파이썬) {
163
+ console.log(`${c.r}✗${c[0]} 파이썬을 못 찾았습니다 — ScrapeGraphAI 는 파이썬 라이브러리입니다`);
164
+ } else {
165
+ console.log(`${c.g}✓${c[0]} 파이썬 ${E.파이썬.버전} ${c.d}(${E.파이썬.명령})${c[0]}`);
166
+ console.log(` ${표(!!E.라이브러리,
167
+ `scrapegraphai ${E.라이브러리}`,
168
+ `scrapegraphai 가 아직 없습니다 ${c.d}→ ${D.스킬.설치}${c[0]}`)}`);
169
+ console.log(` ${표(!!E.플레이라이트, "playwright 모듈",
170
+ `playwright 모듈이 없습니다 ${c.d}→ pip install playwright${c[0]}`)}`);
171
+ if (E.플레이라이트) {
172
+ console.log(` ${표(!!E.브라우저, "브라우저 엔진 내려받음",
173
+ `${c.y}브라우저 엔진이 없습니다${c[0]} ${c.d}→ ${D.스킬.브라우저}${c[0]}`)}`);
174
+ if (!E.브라우저) {
175
+ console.log(` ${c.y}★이게 빠지면 에러 없이 ${c.b}빈 결과${c[0]}${c.y}가 돌아옵니다.${c[0]}`);
176
+ console.log(` ${c.d}자바스크립트로 그리는 사이트에서 본문이 통째로 비어 옵니다.${c[0]}`);
177
+ }
178
+ }
179
+ }
180
+
181
+ console.log("");
182
+ if (E.키.length) console.log(`${c.g}✓${c[0]} LLM 키 ${E.키.join(", ")}`);
183
+ else if (E.올라마) console.log(`${c.g}✓${c[0]} Ollama 가 떠 있습니다 ${c.d}(localhost:11434) — 키 없이 0원으로 돌립니다${c[0]}`);
184
+ else {
185
+ console.log(`${c.y}…${c[0]} LLM 키도 Ollama 도 못 찾았습니다`);
186
+ console.log(` ${c.d}${굵게("라이브러리는 무료지만 **모델은 내 것**입니다.")}${c[0]}`);
187
+ console.log(` ${c.d}둘 중 하나면 됩니다 — 내 API 키를 넣거나, Ollama 를 띄워 로컬로 돌리거나.${c[0]}`);
188
+ }
189
+ console.log(`${c.d}${"─".repeat(58)}${c[0]}`);
190
+ }
191
+
192
+ /* ══ 심기 ═══════════════════════════════════════════════════════ */
193
+
194
+ function 심기() {
195
+ mkdirSync(대상, { recursive: true });
196
+ cpSync(원본, 대상, { recursive: true });
197
+ /* ★쓴 뒤 다시 읽는다 — 복사는 조용히 실패한다 [[kit-update-selfcheck]] */
198
+ const 문제 = [];
199
+ for (const f of readdirSync(원본)) {
200
+ const a = join(원본, f), b = join(대상, f);
201
+ if (!existsSync(b)) { 문제.push(`${f} 가 안 복사됐다`); continue; }
202
+ if (statSync(b).size !== statSync(a).size) 문제.push(`${f} 크기가 다르다 — 잘려 복사됐다`);
203
+ }
204
+ if (!문제.length) {
205
+ try { JSON.parse(readFileSync(join(대상, "scrapegraph.json"), "utf8")); }
206
+ catch { 문제.push("깐 원장이 JSON 으로 안 읽힌다"); }
207
+ }
208
+ return 문제;
209
+ }
210
+
211
+ /* ══ 자기시험 — 네트워크 안 씀 ══════════════════════════════════ */
212
+
213
+ function 자기시험() {
214
+ const 나쁨 = [];
215
+ const L = 원장읽기();
216
+ if (!L.됨) { 나쁨.push(L.이유); return 나쁨; }
217
+ for (const m of 원장검사(L.d)) 나쁨.push(`원장: ${m}`);
218
+
219
+ if (!existsSync(join(원본, "SKILL.md"))) 나쁨.push("skill/SKILL.md 가 없다");
220
+ else {
221
+ const md = readFileSync(join(원본, "SKILL.md"), "utf8");
222
+ if (!/^---[\s\S]*?name:\s*scrapegraph-guide/m.test(md))
223
+ 나쁨.push("SKILL.md 머리말의 name 이 scrapegraph-guide 가 아니다 — 클로드가 못 찾는다");
224
+ if (!/description:/.test(md)) 나쁨.push("SKILL.md 에 description 이 없다 — 발동하지 않는다");
225
+ if (!/playwright install/.test(md))
226
+ 나쁨.push("SKILL.md 가 playwright install 을 말하지 않는다 — 「안 되는데요」의 절반이 그 줄이다");
227
+ if (!/Ollama/i.test(md)) 나쁨.push("SKILL.md 가 로컬(Ollama) 길을 말하지 않는다");
228
+ for (const re of 금칙) if (re.test(md)) 나쁨.push(`SKILL.md 에 조건 없는 단정: ${re.source}`);
229
+ }
230
+
231
+ /* ★검사기 자체를 양방향으로 본다 [[verifier-must-be-tested-both-ways]] */
232
+ const 좋은원장 = JSON.parse(JSON.stringify(L.d));
233
+ if (원장검사(좋은원장).length) 나쁨.push("자기시험 오탐: 멀쩡한 원장을 막는다");
234
+ const 나쁜원장 = JSON.parse(JSON.stringify(L.d));
235
+ delete 나쁜원장["★무료의범위"];
236
+ if (!원장검사(나쁜원장).length) 나쁨.push("자기시험 미탐: 무료의범위가 빠져도 통과한다");
237
+ const 나쁜원장2 = JSON.parse(JSON.stringify(L.d));
238
+ 나쁜원장2.스킬.브라우저 = "";
239
+ if (!원장검사(나쁜원장2).length) 나쁨.push("자기시험 미탐: playwright 줄이 빠져도 통과한다");
240
+
241
+ return 나쁨;
242
+ }
243
+
244
+ /* ══ 끝난 뒤 한 칸 ═══════════════════════════════════════════════
245
+ 규율: ① 끝난 뒤 딱 한 칸 · ② 못 깔았으면 안 띄운다 · ③ 파일을 안 남긴다
246
+ ④ 고지 블록 금지 · ⑤ 첫 줄은 방금 한 일에서 이어져야 한다 */
247
+ function 꼬리() {
248
+ const T = TAIL[퍼널];
249
+ if (!T) return;
250
+ console.log("");
251
+ for (const l of T.이음) console.log(`${c.d}${굵게(l)}${c[0]}`);
252
+ console.log("");
253
+ console.log(` ${c.b}${T.제목}${c[0]} ${c.d}${T.한줄}${c[0]}`);
254
+ console.log(` ${c.cy}${링크(퍼널)}${c[0]}`);
255
+ console.log("");
256
+ console.log(`${c.d}${마지막}${c[0]}`);
257
+ }
258
+
259
+ /* ══ 본체 ═══════════════════════════════════════════════════════ */
260
+
261
+ if (자기시험모드) {
262
+ const 나쁨 = 자기시험();
263
+ if (나쁨.length) {
264
+ console.log(`${c.r}자기시험 실패${c[0]}`);
265
+ for (const m of 나쁨) console.log(" - " + m);
266
+ process.exit(1);
267
+ }
268
+ console.log(`${c.g}자기시험 통과${c[0]}`);
269
+ process.exit(0);
270
+ }
271
+
272
+ const L = 원장읽기();
273
+ if (!L.됨) { console.log(`${c.r}✗ ${L.이유}${c[0]}`); process.exit(1); }
274
+ const 원장문제 = 원장검사(L.d);
275
+ if (원장문제.length) {
276
+ console.log(`${c.r}✗ 원장이 성하지 않다 — 깔지 않는다${c[0]}`);
277
+ for (const m of 원장문제) console.log(" - " + m);
278
+ process.exit(1);
279
+ }
280
+ const D = L.d;
281
+
282
+ console.log("");
283
+ console.log(`${c.b}ScrapeGraphAI${c[0]} ${c.d}— ${D.스킬.라이선스} · ${D.스킬.언어} · GitHub ★${D.숫자.별.toLocaleString("en-US")} (${D.확인일} 실측)${c[0]}`);
284
+
285
+ /* ★★무엇이 무료인지를 **맨 앞에서** 말한다. 깔고 나서 막히는 자리가 여기라서다. */
286
+ console.log("");
287
+ console.log(`${c.d}${굵게("무료인 건 **라이브러리**입니다 — 내 컴퓨터에서 돌고 MIT 입니다.")}${c[0]}`);
288
+ console.log(`${c.d}${굵게("**모델은 내 것**입니다 — 내 API 키를 쓰거나, Ollama 로 로컬로 돌리면 0원입니다.")}${c[0]}`);
289
+ console.log(`${c.d}클라우드 API(scrapegraph-py/js)는 ${굵게("**별개의 유료 서비스**")}입니다.${c[0]}`);
290
+
291
+ const E = 환경재기();
292
+ 환경보고(E, D);
293
+
294
+ if (확인만) {
295
+ console.log(`${c.d}깔려면: npx --yes reborn-scrapegraph${c[0]}\n`);
296
+ process.exit(0);
297
+ }
298
+
299
+ if (미리보기) {
300
+ console.log(`${c.d}할 일:${c[0]}`);
301
+ console.log(` ${스킬이름} → ${대상}`);
302
+ console.log(`${c.d}(--dry-run 이라 아무것도 안 했습니다)${c[0]}\n`);
303
+ process.exit(0);
304
+ }
305
+
306
+ const 문제 = 심기();
307
+ if (문제.length) {
308
+ console.log(`\n${c.r}✗ 심다가 막혔습니다${c[0]}`);
309
+ for (const m of 문제) console.log(" - " + m);
310
+ console.log(`${c.d}권한이나 경로 문제일 수 있습니다. --local 로 현재 폴더에 깔아 보세요.${c[0]}\n`);
311
+ process.exit(1);
312
+ }
313
+
314
+ console.log(`\n${c.g}✓${c[0]} ${c.b}${스킬이름}${c[0]} 을 깔았습니다`);
315
+ console.log(` ${c.d}${대상}${c[0]}`);
316
+ console.log("");
317
+ console.log(`${c.d}클로드 코드를 ${굵게("**다시 시작**")}하면 잡힙니다. 그 뒤 이렇게 물어 보세요:${c[0]}`);
318
+ console.log(` ${c.cy}"이 페이지에서 제품명이랑 가격 뽑아줘"${c[0]}`);
319
+ console.log("");
320
+ console.log(`${c.d}지금 상태만 다시 재려면: ${c[0]}npx --yes reborn-scrapegraph --check`);
321
+
322
+ 꼬리();
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "reborn-scrapegraph",
3
+ "version": "1.0.0",
4
+ "description": "영상에서 소개한 ScrapeGraphAI(MIT 오픈소스 파이썬 스크래퍼)를 실제로 쓸 수 있게 만드는 안내 스킬입니다. 무료인 건 라이브러리이고 모델은 내 키 또는 Ollama 라는 것을 먼저 말하고, playwright 브라우저 엔진이 왔는지·파이썬이 몇 개인지를 이 컴퓨터에서 직접 재 줍니다.",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "agent-skills",
9
+ "skill",
10
+ "scrapegraphai",
11
+ "scraping",
12
+ "web-scraping",
13
+ "crawler",
14
+ "playwright",
15
+ "ollama",
16
+ "korean"
17
+ ],
18
+ "homepage": "https://rebornlabs.kr/scrapegraph?utm_source=installer&utm_medium=cli&utm_campaign=scrape",
19
+ "bugs": {
20
+ "url": "https://mobility.rebornlabs.kr/cs"
21
+ },
22
+ "license": "MIT",
23
+ "author": "REBORN LABS",
24
+ "type": "module",
25
+ "bin": {
26
+ "reborn-scrapegraph": "cli.mjs"
27
+ },
28
+ "files": [
29
+ "cli.mjs",
30
+ "tail.mjs",
31
+ "skill/",
32
+ "README.md",
33
+ "안내스킬.md"
34
+ ],
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "scripts": {
39
+ "test": "node cli.mjs --self-test",
40
+ "check": "node cli.mjs --check"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: scrapegraph-guide
3
+ description: ScrapeGraphAI(MIT 오픈소스 파이썬 스크래퍼)로 웹페이지에서 구조화된 데이터를 뽑을 때 쓴다. 주소 한 줄과 "무엇을 뽑을지" 한 문장으로 JSON 을 받는 작업 — 경쟁사 가격표, 리드 목록, 기사 요지, 채용 조건 수집. 사용자가 "긁어와", "스크래핑", "크롤링", "이 사이트에서 뽑아줘", "scrapegraphai", "스크랩그래프" 라고 하거나, 웹에서 반복 수집이 필요한 일을 말할 때 발동한다. ★먼저 무엇이 무료이고 무엇이 유료인지, 그리고 지금 이 컴퓨터에서 무엇이 빠졌는지 말한다.
4
+ ---
5
+
6
+ # ScrapeGraphAI — 주소 한 줄 + 원하는 것 한 문장
7
+
8
+ ## ★가장 먼저 말할 것 — 무엇이 무료인가
9
+
10
+ ScrapeGraphAI 는 **두 가지 물건**이다. 이걸 안 나누면 사용자가 깔고 나서 막힌다.
11
+
12
+ | | 오픈소스 라이브러리 | 매니지드 클라우드 API |
13
+ |---|---|---|
14
+ | 무엇 | `scrapegraphai` — **내가 돌리는 파이썬 라이브러리** | `scrapegraph-py` / `scrapegraph-js` — 남이 돌려 주는 호스팅 서비스 |
15
+ | 어디서 도나 | 내 컴퓨터 | ScrapeGraphAI 클라우드 |
16
+ | 값 | **MIT · 무료** | **별개의 유료 서비스** |
17
+ | LLM | **내 키**(OpenAI·Groq·Gemini·Azure) 또는 **Ollama 로컬** | 저쪽이 관리 |
18
+ | 브라우저 렌더링 | 내가 붙인다(Playwright) | 저쪽이 관리 |
19
+
20
+ **「무료」는 왼쪽 칸 이야기다.** 라이브러리는 정말 무료이고, Ollama 로 로컬 모델을 쓰면
21
+ **한 푼도 안 든다** — 대신 내 PC 가 돈다. 클라우드 키를 쓰면 그건 유료 서비스다.
22
+
23
+ > 소개 영상들이 이 표를 화면에 띄우면서 「무료 오픈소스」라고만 말하는 경우가 많다.
24
+ > 틀린 말은 아니다 — 다만 화면에 답이 떠 있는데 말하지 않았을 뿐이다.
25
+ > 사용자가 깔고 나서 「키를 넣으래요」로 막히는 자리가 정확히 여기다.
26
+
27
+ ## ★두 번째로 할 것 — 지금 이 컴퓨터를 잰다
28
+
29
+ 말로 「되실 겁니다」 하지 않는다. **실제로 본다.**
30
+
31
+ ```bash
32
+ python -c "import scrapegraphai, sys; print(scrapegraphai.__version__)" # 라이브러리
33
+ python -c "import playwright; print('playwright ok')" # 브라우저 엔진
34
+ ```
35
+
36
+ ★파이썬은 **여러 개가 깔려 있을 수 있다.** `python --version` 이 아니라 `import` 로 고른다
37
+ — 버전만 보고 고르면 껍데기 인터프리터를 잡는다.
38
+
39
+ ```bash
40
+ py -3.11 -c "import scrapegraphai" # 되는 쪽이 정본이다
41
+ py -3.12 -c "import scrapegraphai"
42
+ ```
43
+
44
+ ## 설치 — 두 줄이다. 한 줄이 아니다
45
+
46
+ ```bash
47
+ pip install scrapegraphai
48
+ playwright install
49
+ ```
50
+
51
+ ★**`playwright install` 이 빠지면 조용히 빈 결과가 나온다.** 에러가 안 난다 —
52
+ 자바스크립트로 그리는 사이트(요즘 대부분)에서 본문이 통째로 비어 돌아온다.
53
+ 「안 되는데요」의 절반이 이 한 줄이다. 설치 안내에서 빼지 마라.
54
+
55
+ ## 쓰는 법 — 스키마를 먼저 정한다
56
+
57
+ ```python
58
+ from scrapegraphai.graphs import SmartScraperGraph
59
+
60
+ graph = SmartScraperGraph(
61
+ prompt="제품명, 가격, 재고, 별점, 리뷰 수를 뽑아줘",
62
+ source="https://shop.example.com/item/1234",
63
+ config={
64
+ "llm": {"model": "openai/gpt-4o-mini", "api_key": "..."},
65
+ "verbose": False,
66
+ },
67
+ )
68
+ print(graph.run())
69
+ ```
70
+
71
+ ### 로컬로 완전 0원
72
+
73
+ ```python
74
+ config = {
75
+ "llm": {"model": "ollama/llama3.1", "model_tokens": 8192,
76
+ "base_url": "http://localhost:11434"},
77
+ "embeddings": {"model": "ollama/nomic-embed-text",
78
+ "base_url": "http://localhost:11434"},
79
+ }
80
+ ```
81
+
82
+ ★Ollama 를 먼저 띄워 둬야 한다(`ollama serve`). 안 띄우면 연결 거부로 죽는다.
83
+
84
+ ### 그래프 고르기
85
+
86
+ | 무엇을 하나 | 클래스 |
87
+ |---|---|
88
+ | 페이지 하나에서 구조화 데이터 | `SmartScraperGraph` |
89
+ | 페이지 **여러 개**를 같은 스키마로 | `SmartScraperMultiGraph` |
90
+ | 검색부터 해서 모아 오기 | `SearchGraph` |
91
+ | 스크래핑 코드를 짜 달라고 하기 | `ScriptCreatorGraph` |
92
+
93
+ ## ★답할 때 지키는 것
94
+
95
+ 1. **무료의 범위를 먼저 말한다.** 라이브러리는 무료, 모델은 내 키 또는 로컬.
96
+ 2. **잰 것만 말한다.** 사용자 PC 에 뭐가 깔렸는지는 위 명령으로 **확인하고** 말한다.
97
+ 3. **`playwright install` 을 빠뜨리지 않는다.**
98
+ 4. **파이썬은 `import` 로 고른다.** 버전 문자열로 고르지 않는다.
99
+ 5. **긁어도 되는지는 사용자가 판단할 일이다.** 대상 사이트의 이용약관·`robots.txt`·
100
+ 개인정보가 걸릴 수 있다는 것을 한 줄로 알린다. 법률 판단을 대신 내리지 않는다 —
101
+ 「써도 됩니다」·「문제없습니다」로 끝내지 말고, 무엇을 확인해야 하는지를 말한다.
102
+ 6. **로그인 뒤 페이지·차단된 사이트는 안 된다고 말한다.** 우회 방법을 안내하지 않는다.
103
+
104
+ ## 숫자의 주인은 `scrapegraph.json` 하나다
105
+
106
+ 별 개수·라이선스·공급자 목록은 **원장에서만** 나온다. 여기 본문에 다시 적지 않는다 —
107
+ 두 곳에 적으면 두 곳이 서로 다른 말을 하기 시작한다.
108
+ 원장에는 **확인일과 확인방법**이 같이 있다. 확인일이 없는 숫자는 지어낸 것과 구분이 안 된다.
@@ -0,0 +1,55 @@
1
+ {
2
+ "_doc": "ScrapeGraphAI 사실 원장 — 릴스·착지·DM·설치기가 **전부 이 파일 하나**를 본다. 지어낸 수치가 화면에 오르는 것을 막는다.",
3
+ "확인일": "2026-09-01",
4
+ "확인방법": "GitHub REST API(stargazers_count·forks_count·subscribers_count·license·language)로 저장소를 직접 조회하고, main 브랜치 README.md 원문을 내려받아 설치 명령·LLM 공급자·그래프 클래스·비교표를 직접 읽었다.",
5
+ "저장소": {
6
+ "주소": "https://github.com/ScrapeGraphAI/Scrapegraph-ai",
7
+ "설명": "Python scraper based on AI"
8
+ },
9
+ "숫자": {
10
+ "별": 30233,
11
+ "포크": 2999,
12
+ "관심": 178
13
+ },
14
+ "스킬": {
15
+ "라이선스": "MIT",
16
+ "언어": "Python",
17
+ "설치": "pip install scrapegraphai",
18
+ "브라우저": "playwright install"
19
+ },
20
+ "그래프": [
21
+ "SmartScraperGraph",
22
+ "SearchGraph",
23
+ "SpeechGraph",
24
+ "ScriptCreatorGraph",
25
+ "SmartScraperMultiGraph",
26
+ "ScriptCreatorMultiGraph"
27
+ ],
28
+ "LLM공급자": ["OpenAI", "Groq", "Azure", "Gemini", "MiniMax", "Ollama(로컬)"],
29
+
30
+ "★무료의범위": {
31
+ "_왜": "★★이 회차에서 우리가 참고본에 **더하는 한 줄**이다. 참고본은 이 비교표를 화면에 띄우고도 「무료 오픈소스」라고만 말한다. 틀린 말은 아니다 — 다만 화면에 답이 떠 있는데 말하지 않았을 뿐이다.",
32
+ "라이브러리": "MIT · 무료 · 내 컴퓨터에서 돈다",
33
+ "LLM": "★내 키를 쓴다. Ollama 로 로컬 모델을 쓰면 그것도 0원이다.",
34
+ "클라우드API": "scrapegraph-py / scrapegraph-js 는 **별개의 유료 호스팅 서비스**다. 라이브러리와 다른 물건이다.",
35
+ "원문근거": "README — ScrapeGraphAI comes in two flavours: this open-source library, which you run yourself, and the managed cloud API",
36
+ "비교표": [
37
+ ["What it is", "A Python library you run yourself", "A hosted cloud service you call via SDK"],
38
+ ["Where it runs", "Your own infrastructure (self-hosted)", "ScrapeGraphAI cloud"],
39
+ ["LLM", "Bring your own (OpenAI, Groq, Gemini, Azure, local via Ollama)", "Managed for you"],
40
+ ["Browser / JS rendering", "You configure it (Playwright)", "Managed (stealth, auto/fast/js modes)"]
41
+ ]
42
+ },
43
+
44
+ "★참고본이_말하지_않은_것": [
45
+ "★★「무료 오픈소스」는 **라이브러리 이야기**다. 화면에 띄운 비교표 오른쪽 칸(Managed API)은 유료 호스팅이고, 영상은 그 표를 띄우면서 그 구분을 말하지 않는다. 이 한 줄이 우리가 더하는 전부다.",
46
+ "★라이브러리를 돌리려면 **LLM 키가 필요하다.** 없으면 Ollama 로 로컬 모델을 쓴다 — 그러면 완전히 0원이지만 대신 내 PC 가 돈다.",
47
+ "★설치가 pip 한 줄로 끝나지 않는다 — playwright install 이 한 줄 더 있다. 이게 빠지면 자바스크립트로 그리는 사이트에서 조용히 빈 결과가 나온다."
48
+ ],
49
+
50
+ "MCP": {
51
+ "저장소": "https://github.com/ScrapeGraphAI/scrapegraph-mcp",
52
+ "라이선스": "MIT",
53
+ "주의": "★공식 MCP 서버는 **클라우드 API 키**를 쓴다. 무료 경로는 MCP 가 아니라 라이브러리다."
54
+ }
55
+ }
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
+ scrape: {
15
+ 이음: [
16
+ "방금 한 일은 **깔기 전에 재 본 것**입니다 — 파이썬이 몇 개인지, 브라우저 엔진이 왔는지, 모델은 어디서 오는지.",
17
+ "도구마다 이 세 가지가 다르고, 매번 이만큼 걸립니다. 문서에는 안 적혀 있으니까요.",
18
+ ],
19
+ 제목: "리본랩스 스킬팩",
20
+ 한줄: "같은 방식으로 골라 담은 48종을 한 줄로. 8,900원",
21
+ },
22
+ };
23
+
24
+ export const 마지막 = "목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.";
@@ -0,0 +1,53 @@
1
+ # 안내스킬 — `scrapegraph-guide`
2
+
3
+ 이 패키지가 `~/.claude/skills/scrapegraph-guide/` 에 심는 것.
4
+
5
+ | 파일 | 무엇 |
6
+ |---|---|
7
+ | `SKILL.md` | 클로드가 읽는 안내. 답하는 순서가 여기 있다 |
8
+ | `scrapegraph.json` | **원장.** 모든 숫자의 주인. 여기 없는 숫자는 말하지 않는다 |
9
+
10
+ ★심는 것은 이 폴더 하나뿐이다. **공유 안내스킬에는 손대지 않는다** —
11
+ 같은 파일을 여러 설치기가 덮는 사고를 애초에 안 만든다.
12
+
13
+ ## 왜 원장을 따로 두나
14
+
15
+ 숫자를 `SKILL.md` 본문에 적으면 두 곳이 서로 다른 말을 하기 시작한다.
16
+ 별 개수·라이선스·공급자 목록은 **한 곳에서만** 나온다. [[one-metric-one-owner]]
17
+
18
+ 원장에는 **확인일과 확인방법**이 반드시 있다. 없으면 설치기가 막는다.
19
+
20
+ ## ★이 킷의 존재 이유는 `★무료의범위` 다
21
+
22
+ 참고본(그리고 대부분의 소개 영상)은 「Open Source vs Managed API」 비교표를
23
+ **화면에 띄우면서** 「무료 오픈소스」라고만 말한다. 틀린 말은 아니다 —
24
+ 화면에 답이 떠 있는데 말하지 않았을 뿐이다.
25
+
26
+ 깔고 나서 「키를 넣으래요」로 막히는 자리가 정확히 거기다.
27
+
28
+ 원장검사가 이걸 지킨다 — `★무료의범위` 가 비거나 클라우드 API 를 유료라고
29
+ 말하지 않으면 **통과시키지 않는다.** 그게 빠지면 이 패키지는 존재할 이유가 없다.
30
+
31
+ ## ★`--check` 는 판정을 우리가 하지 않는다
32
+
33
+ 「되실 겁니다」라고 말하지 않는다. `import` 를 **실제로 돌린다.**
34
+
35
+ - 파이썬은 **버전 문자열이 아니라 import 로** 고른다 [[python-interpreter-must-be-probed]]
36
+ - playwright 는 **모듈이 있나**와 **브라우저 엔진이 왔나**를 나눠 본다 —
37
+ 후자가 없으면 **에러 없이 빈 결과**가 나온다. 이게 「안 되는데요」의 절반이다
38
+ - Ollama 는 깔렸는지가 아니라 **응답하는지**를 본다
39
+
40
+ ## 안 되면 제일 먼저
41
+
42
+ 깐 다음 **클로드 코드를 껐다 켜세요.** 그래도 안 뜨면 이름을 직접 부르시고,
43
+ 그래도 없으면 지금 상태를 재 봅니다 — `npx --yes reborn-scrapegraph --check`
44
+
45
+ ---
46
+
47
+ 방금 한 일은 **깔기 전에 재 본 것**입니다 — 파이썬이 몇 개인지, 브라우저 엔진이 왔는지,
48
+ 모델은 어디서 오는지. 도구마다 이 세 가지가 다르고 문서에는 안 적혀 있습니다.
49
+
50
+ **리본랩스 스킬팩** — 같은 방식으로 골라 담은 48종을 한 줄로. 8,900원
51
+ https://rebornlabs.kr/skillpack?utm_source=installer&utm_medium=cli&utm_campaign=scrape
52
+
53
+ 목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.