wormclaude 1.0.214 → 1.0.216
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/dist/clone-render.js +170 -0
- package/dist/clone.js +11 -7
- package/dist/theme.js +1 -1
- package/dist/tools.js +37 -10
- package/package.json +2 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Render-modlu klonlama — gerçek (headless) Chrome/Edge başlatır, JS'i ÇALIŞTIRIR, sayfayı
|
|
2
|
+
// render eder ve network'ten geçen TÜM asset'leri (css/js/img/font) yakalar. SPA (React/Vue)
|
|
3
|
+
// ve Cloudflare/bot-koruması arkasındaki siteleri yakalar (gerçek tarayıcı = JS-challenge geçer).
|
|
4
|
+
// puppeteer-core kullanır (Chromium İNDİRMEZ) → sistemde kurulu Chrome/Edge'i çalıştırır.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { pageFile, assetFile, relHref, ASSET_EXT, PAGE_EXT } from './clone.js';
|
|
8
|
+
// Sistemde kurulu Chrome/Edge'i bul (puppeteer kendi Chromium'unu indirmesin)
|
|
9
|
+
export function findChrome() {
|
|
10
|
+
const win = [
|
|
11
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
12
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
13
|
+
(process.env.LOCALAPPDATA || '') + '\\Google\\Chrome\\Application\\chrome.exe',
|
|
14
|
+
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
15
|
+
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
16
|
+
];
|
|
17
|
+
const mac = [
|
|
18
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
19
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
20
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
21
|
+
];
|
|
22
|
+
const linux = ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/microsoft-edge'];
|
|
23
|
+
const cands = process.platform === 'win32' ? win : process.platform === 'darwin' ? mac : linux;
|
|
24
|
+
for (const c of cands) {
|
|
25
|
+
try {
|
|
26
|
+
if (c && fs.existsSync(c))
|
|
27
|
+
return c;
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export async function cloneSiteRendered(startUrl, outDir, opts = {}) {
|
|
34
|
+
const maxPages = Math.min(Math.max(opts.maxPages ?? 20, 1), 100);
|
|
35
|
+
if (!/^https?:\/\//i.test(startUrl))
|
|
36
|
+
startUrl = 'https://' + startUrl;
|
|
37
|
+
const exe = findChrome();
|
|
38
|
+
if (!exe)
|
|
39
|
+
throw new Error('Chrome/Edge bulunamadı — render modu için gerekli. Google Chrome kurun.');
|
|
40
|
+
let puppeteer;
|
|
41
|
+
try {
|
|
42
|
+
puppeteer = (await import('puppeteer-core')).default;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new Error('puppeteer-core kurulu değil — `npm i -g puppeteer-core` veya CLI\'yi güncelle.');
|
|
46
|
+
}
|
|
47
|
+
const start = new URL(startUrl);
|
|
48
|
+
const host = start.host;
|
|
49
|
+
const root = path.resolve(outDir);
|
|
50
|
+
const assetsDir = path.join(root, '_assets');
|
|
51
|
+
fs.mkdirSync(root, { recursive: true });
|
|
52
|
+
const errors = [];
|
|
53
|
+
const assetMap = new Map(); // absUrl(hash'siz) → yerel dosya
|
|
54
|
+
const pageMap = new Map();
|
|
55
|
+
const visited = new Set();
|
|
56
|
+
const queue = [start.href.replace(/#.*$/, '')];
|
|
57
|
+
const rendered = [];
|
|
58
|
+
const browser = await puppeteer.launch({
|
|
59
|
+
executablePath: exe, headless: true,
|
|
60
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage'],
|
|
61
|
+
});
|
|
62
|
+
try {
|
|
63
|
+
const page = await browser.newPage();
|
|
64
|
+
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36');
|
|
65
|
+
// Network'ten geçen her asset'i yakala (HTML parse'tan çok daha eksiksiz)
|
|
66
|
+
page.on('response', async (res) => {
|
|
67
|
+
try {
|
|
68
|
+
const rt = res.request().resourceType(); // document/stylesheet/script/image/font/media/xhr...
|
|
69
|
+
if (!['stylesheet', 'script', 'image', 'font', 'media'].includes(rt))
|
|
70
|
+
return;
|
|
71
|
+
const u = res.url().replace(/#.*$/, '');
|
|
72
|
+
if (u.startsWith('data:') || assetMap.has(u) || !res.ok())
|
|
73
|
+
return;
|
|
74
|
+
const buf = await res.buffer();
|
|
75
|
+
const dest = assetFile(new URL(u), assetsDir);
|
|
76
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
77
|
+
fs.writeFileSync(dest, buf);
|
|
78
|
+
assetMap.set(u, dest);
|
|
79
|
+
}
|
|
80
|
+
catch { /* yoksay */ }
|
|
81
|
+
});
|
|
82
|
+
while (queue.length && rendered.length < maxPages) {
|
|
83
|
+
const cur = queue.shift();
|
|
84
|
+
if (visited.has(cur))
|
|
85
|
+
continue;
|
|
86
|
+
visited.add(cur);
|
|
87
|
+
try {
|
|
88
|
+
await page.goto(cur, { waitUntil: 'domcontentloaded', timeout: 45000 });
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
errors.push(`page ${cur}: ${String(e.message).slice(0, 80)}`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Cloudflare / bot-challenge'ın kendini çözmesini bekle (max ~28s) — yoksa "Bir dakika lütfen" sayfası yakalanır
|
|
95
|
+
const CH = /just a moment|bir dakika|checking your browser|attention required|please wait|moment\.\.\./i;
|
|
96
|
+
for (let i = 0; i < 14; i++) {
|
|
97
|
+
let t = '';
|
|
98
|
+
try {
|
|
99
|
+
t = await page.title();
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
const sel = await page.$('#challenge-running, #cf-challenge-running, .cf-browser-verification, #cf-please-wait').catch(() => null);
|
|
103
|
+
if (!CH.test(t) && !sel)
|
|
104
|
+
break;
|
|
105
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
106
|
+
}
|
|
107
|
+
await page.waitForNetworkIdle({ idleTime: 1200, timeout: 12000 }).catch(() => { });
|
|
108
|
+
await new Promise((r) => setTimeout(r, 1500)); // JS'in oturması + geç asset'ler
|
|
109
|
+
let title = '';
|
|
110
|
+
try {
|
|
111
|
+
title = await page.title();
|
|
112
|
+
}
|
|
113
|
+
catch { }
|
|
114
|
+
if (CH.test(title)) { // hâlâ challenge → boş "bekleyin" sayfasını KAYDETME
|
|
115
|
+
errors.push(`page ${cur}: bot-koruması (Cloudflare) challenge geçilemedi`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const u = new URL(cur);
|
|
119
|
+
const file = pageFile(u, root);
|
|
120
|
+
pageMap.set(cur, file);
|
|
121
|
+
rendered.push({ url: u, file, html: await page.content() }); // RENDER OLMUŞ DOM
|
|
122
|
+
// render olmuş DOM'dan iç linkleri keşfet (SPA'da JS ile gelen linkler dahil)
|
|
123
|
+
let links = [];
|
|
124
|
+
try {
|
|
125
|
+
links = await page.$$eval('a[href]', (as) => as.map((a) => a.href));
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
for (const href of links) {
|
|
129
|
+
try {
|
|
130
|
+
const abs = new URL(href);
|
|
131
|
+
abs.hash = '';
|
|
132
|
+
if (abs.host === host && !ASSET_EXT.test(abs.pathname) && !visited.has(abs.href) &&
|
|
133
|
+
rendered.length + queue.length < maxPages &&
|
|
134
|
+
(PAGE_EXT.test(abs.pathname) || !/\.[a-z0-9]{1,6}$/i.test(abs.pathname.split('/').pop() || ''))) {
|
|
135
|
+
queue.push(abs.href);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch { }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await browser.close().catch(() => { });
|
|
144
|
+
}
|
|
145
|
+
// PASS 2: render olmuş HTML'de asset+sayfa yollarını yerele düzelt
|
|
146
|
+
for (const pg of rendered) {
|
|
147
|
+
let html = pg.html;
|
|
148
|
+
const refs = new Set();
|
|
149
|
+
for (const m of html.matchAll(/(?:href|src)\s*=\s*["']([^"']+)["']/gi))
|
|
150
|
+
refs.add(m[1]);
|
|
151
|
+
for (const ref of refs) {
|
|
152
|
+
let abs;
|
|
153
|
+
try {
|
|
154
|
+
abs = new URL(ref, pg.url.href);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const bare = abs.href.replace(/#.*$/, '');
|
|
160
|
+
const local = assetMap.get(bare) || pageMap.get(bare);
|
|
161
|
+
if (local) {
|
|
162
|
+
const rel = relHref(pg.file, local);
|
|
163
|
+
html = html.split(`"${ref}"`).join(`"${rel}"`).split(`'${ref}'`).join(`'${rel}'`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
fs.mkdirSync(path.dirname(pg.file), { recursive: true });
|
|
167
|
+
fs.writeFileSync(pg.file, html);
|
|
168
|
+
}
|
|
169
|
+
return { pages: rendered.length, assets: assetMap.size, outDir: root, pageFiles: rendered.map((p) => p.file), errors: errors.slice(0, 30) };
|
|
170
|
+
}
|
package/dist/clone.js
CHANGED
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
// wget --mirror / HTTrack mantığı, aynı-host, sayfa limitli. Bağımlılık yok (regex tabanlı).
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
const UA = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export const UA = {
|
|
8
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
|
9
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
10
|
+
'Accept-Language': 'tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
11
|
+
};
|
|
12
|
+
export const ASSET_EXT = /\.(css|js|mjs|png|jpe?g|gif|webp|avif|svg|ico|bmp|woff2?|ttf|eot|otf|mp4|webm|ogg|mp3|wav|pdf|json|xml|map)(\?.*)?$/i;
|
|
13
|
+
export const PAGE_EXT = /\.(html?|php|aspx?|jsp|cfm)(\?.*)?$/i;
|
|
14
|
+
export function san(s) { return s.replace(/(\?|%3F|#|%23).*$/i, '').replace(/[^a-zA-Z0-9._/-]/g, '_'); }
|
|
11
15
|
// URL → yerel dosya yolu (sayfa). / → index.html, /about → about/index.html, /a.html → a.html
|
|
12
|
-
function pageFile(u, root) {
|
|
16
|
+
export function pageFile(u, root) {
|
|
13
17
|
let p = decodeURIComponent(u.pathname);
|
|
14
18
|
const last = p.split('/').pop() || '';
|
|
15
19
|
if (p.endsWith('/') || last === '')
|
|
@@ -21,13 +25,13 @@ function pageFile(u, root) {
|
|
|
21
25
|
return path.join(root, san(p).replace(/^[/\\]+/, ''));
|
|
22
26
|
}
|
|
23
27
|
// URL → yerel asset dosyası (host+path bazlı, _assets altında)
|
|
24
|
-
function assetFile(u, assetsDir) {
|
|
28
|
+
export function assetFile(u, assetsDir) {
|
|
25
29
|
let rel = san(u.host + u.pathname);
|
|
26
30
|
if (!/\.[a-z0-9]{1,6}$/i.test(rel.split('/').pop() || ''))
|
|
27
31
|
rel += '.bin';
|
|
28
32
|
return path.join(assetsDir, rel);
|
|
29
33
|
}
|
|
30
|
-
function relHref(fromFile, toFile) {
|
|
34
|
+
export function relHref(fromFile, toFile) {
|
|
31
35
|
let r = path.relative(path.dirname(fromFile), toFile).replace(/\\/g, '/');
|
|
32
36
|
if (!r.startsWith('.'))
|
|
33
37
|
r = './' + r;
|
package/dist/theme.js
CHANGED
package/dist/tools.js
CHANGED
|
@@ -449,15 +449,18 @@ export const toolSchemas = [
|
|
|
449
449
|
function: {
|
|
450
450
|
name: 'CloneSite',
|
|
451
451
|
description: 'Tam SİTE KLONLAMA (mirror) — gerçek HTML+CSS+JS+görselleri indirip BİREBİR yerel kopya yazar. ' +
|
|
452
|
-
'Kullanıcı bir siteyi "klonla / kopyala / indir / mirror / tam klon" istediğinde BUNU kullan (WebFetch DEĞİL
|
|
453
|
-
'
|
|
454
|
-
'
|
|
452
|
+
'Kullanıcı bir siteyi "klonla / kopyala / indir / mirror / tam klon" istediğinde BUNU kullan (WebFetch DEĞİL). ' +
|
|
453
|
+
'İç linkleri takip eder, asset yollarını yerele düzeltir → çıktı tarayıcıda offline açılır. ' +
|
|
454
|
+
'İKİ MOD: varsayılan hızlı (fetch); başarısız olursa (Cloudflare/SPA → 0 sayfa) OTOMATİK gerçek Chrome (render) ' +
|
|
455
|
+
'moduna geçer. SPA/React/Cloudflare sitesi olduğunu biliyorsan render=true ver. ' +
|
|
456
|
+
'ÖNEMLİ: araç "0 sayfa" veya hata dönerse KLON OLUŞMAMIŞTIR — kullanıcıya dürüst söyle, "indirildi" deme.',
|
|
455
457
|
parameters: {
|
|
456
458
|
type: 'object',
|
|
457
459
|
properties: {
|
|
458
460
|
url: { type: 'string', description: 'Klonlanacak sitenin başlangıç URL\'i (örn. https://site.com)' },
|
|
459
461
|
out_dir: { type: 'string', description: 'Çıktı klasörü (örn. ./site-klon). Yoksa oluşturulur.' },
|
|
460
462
|
max_pages: { type: 'number', description: 'En fazla kaç sayfa gezilsin (varsayılan 30, üst sınır 200). Tek sayfa için 1.' },
|
|
463
|
+
render: { type: 'boolean', description: 'true → gerçek (headless) Chrome ile JS render ederek klonla (SPA/React/Cloudflare siteleri için). Varsayılan false (hızlı fetch; başarısızsa zaten otomatik render\'a geçer).' },
|
|
461
464
|
},
|
|
462
465
|
required: ['url', 'out_dir'],
|
|
463
466
|
},
|
|
@@ -1771,20 +1774,44 @@ export async function executeTool(name, args) {
|
|
|
1771
1774
|
if (!url || !outDir)
|
|
1772
1775
|
return { ok: false, output: 'url ve out_dir gerekli' };
|
|
1773
1776
|
const maxPages = Number(args.max_pages) || 30;
|
|
1774
|
-
|
|
1775
|
-
|
|
1777
|
+
const wantRender = args.render === true || args.render === 'true';
|
|
1778
|
+
const fmt = (r, mode) => {
|
|
1776
1779
|
const lines = [
|
|
1777
|
-
`✓ Klon tamam: ${r.pages} sayfa + ${r.assets} asset
|
|
1778
|
-
`Aç: ${path.join(r.outDir, 'index.html')} (tarayıcıda offline açılır,
|
|
1780
|
+
`✓ Klon tamam (${mode}): ${r.pages} sayfa + ${r.assets} asset → ${r.outDir}`,
|
|
1781
|
+
`Aç: ${path.join(r.outDir, 'index.html')} (tarayıcıda offline açılır, yollar yerele düzeltildi)`,
|
|
1779
1782
|
];
|
|
1780
1783
|
if (r.pageFiles.length)
|
|
1781
1784
|
lines.push('Sayfalar: ' + r.pageFiles.slice(0, 12).map((f) => path.relative(r.outDir, f)).join(', ') + (r.pageFiles.length > 12 ? ` (+${r.pageFiles.length - 12})` : ''));
|
|
1782
1785
|
if (r.errors.length)
|
|
1783
|
-
lines.push(`Atlanan
|
|
1784
|
-
return
|
|
1786
|
+
lines.push(`Atlanan ${r.errors.length} kaynak (ölü link/erişim).`);
|
|
1787
|
+
return lines.join('\n');
|
|
1788
|
+
};
|
|
1789
|
+
try {
|
|
1790
|
+
// 1) Açıkça render istendi → doğrudan gerçek Chrome
|
|
1791
|
+
if (wantRender) {
|
|
1792
|
+
const { cloneSiteRendered } = await import('./clone-render.js');
|
|
1793
|
+
const r = await cloneSiteRendered(url, outDir, { maxPages });
|
|
1794
|
+
if (r.pages === 0)
|
|
1795
|
+
return { ok: false, output: `❌ KLONLANAMADI — render modu da 0 sayfa: ${r.errors.slice(0, 2).join(' | ') || 'sayfa yüklenemedi'}. KULLANICIYA DÜRÜST SÖYLE.` };
|
|
1796
|
+
return { ok: true, output: fmt(r, 'render/headless Chrome') };
|
|
1797
|
+
}
|
|
1798
|
+
// 2) Hızlı (fetch) mod
|
|
1799
|
+
const r = await cloneSite(url, outDir, { maxPages });
|
|
1800
|
+
if (r.pages > 0)
|
|
1801
|
+
return { ok: true, output: fmt(r, 'hızlı/fetch') };
|
|
1802
|
+
// 3) 0 sayfa → Cloudflare/SPA ihtimali → OTOMATİK render moduna geç
|
|
1803
|
+
const why = r.errors.length ? r.errors.slice(0, 2).join(' | ') : 'sayfa çekilemedi';
|
|
1804
|
+
const { cloneSiteRendered, findChrome } = await import('./clone-render.js');
|
|
1805
|
+
if (!findChrome()) {
|
|
1806
|
+
return { ok: false, output: `❌ KLONLANAMADI — hızlı mod 0 sayfa (${why}). Site Cloudflare/SPA arkasında, render modu gerekir ama Chrome/Edge bulunamadı (kurun). KULLANICIYA DÜRÜST SÖYLE: klon OLUŞMADI, "indirildi" DEME.` };
|
|
1807
|
+
}
|
|
1808
|
+
const r2 = await cloneSiteRendered(url, outDir, { maxPages });
|
|
1809
|
+
if (r2.pages > 0)
|
|
1810
|
+
return { ok: true, output: fmt(r2, 'render/gerçek Chrome — hızlı mod engellendi (Cloudflare/SPA), gerçek tarayıcıyla yapıldı') };
|
|
1811
|
+
return { ok: false, output: `❌ KLONLANAMADI — hızlı mod (${why}) ve render modu (${r2.errors.slice(0, 1).join('') || 'yüklenemedi'}) ikisi de başarısız. KULLANICIYA DÜRÜST SÖYLE: klon OLUŞMADI, "indirildi" DEME.` };
|
|
1785
1812
|
}
|
|
1786
1813
|
catch (e) {
|
|
1787
|
-
return { ok: false, output: 'Klonlama hatası: ' + (e?.message || String(e)) };
|
|
1814
|
+
return { ok: false, output: '❌ Klonlama hatası: ' + (e?.message || String(e)) + '. KULLANICIYA DÜRÜST SÖYLE: klon oluşmadı.' };
|
|
1788
1815
|
}
|
|
1789
1816
|
}
|
|
1790
1817
|
if (name === 'WebSearch') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wormclaude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.216",
|
|
4
4
|
"description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"pdf-parse": "^2.4.5",
|
|
26
26
|
"pdfkit": "^0.19.1",
|
|
27
27
|
"pptxgenjs": "^4.0.1",
|
|
28
|
+
"puppeteer-core": "^25.2.1",
|
|
28
29
|
"react": "^18.3.1",
|
|
29
30
|
"string-width": "^7.2.0"
|
|
30
31
|
},
|