wormclaude 1.0.213 → 1.0.214
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.js +184 -0
- package/dist/theme.js +1 -1
- package/dist/tools.js +51 -1
- package/package.json +1 -1
package/dist/clone.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Tam-site klonlama (mirror) — gerçek baytları çeker, birebir yerel kopya yazar.
|
|
2
|
+
// WebFetch HTML'i siler (sadece metin) → klon için işe yaramaz; bu modül ham HTML + tüm
|
|
3
|
+
// asset'leri (CSS/JS/görsel/font) indirir, iç linkleri+asset yollarını yerele düzeltir.
|
|
4
|
+
// wget --mirror / HTTrack mantığı, aynı-host, sayfa limitli. Bağımlılık yok (regex tabanlı).
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
const UA = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36' };
|
|
8
|
+
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;
|
|
9
|
+
const PAGE_EXT = /\.(html?|php|aspx?|jsp|cfm)(\?.*)?$/i;
|
|
10
|
+
function san(s) { return s.replace(/(\?|%3F|#|%23).*$/i, '').replace(/[^a-zA-Z0-9._/-]/g, '_'); }
|
|
11
|
+
// URL → yerel dosya yolu (sayfa). / → index.html, /about → about/index.html, /a.html → a.html
|
|
12
|
+
function pageFile(u, root) {
|
|
13
|
+
let p = decodeURIComponent(u.pathname);
|
|
14
|
+
const last = p.split('/').pop() || '';
|
|
15
|
+
if (p.endsWith('/') || last === '')
|
|
16
|
+
p += 'index.html';
|
|
17
|
+
else if (!/\.[a-z0-9]{1,6}$/i.test(last))
|
|
18
|
+
p += '/index.html';
|
|
19
|
+
else if (!/\.html?$/i.test(last) && PAGE_EXT.test(last))
|
|
20
|
+
p = p.replace(/\.[^.]+$/, '.html');
|
|
21
|
+
return path.join(root, san(p).replace(/^[/\\]+/, ''));
|
|
22
|
+
}
|
|
23
|
+
// URL → yerel asset dosyası (host+path bazlı, _assets altında)
|
|
24
|
+
function assetFile(u, assetsDir) {
|
|
25
|
+
let rel = san(u.host + u.pathname);
|
|
26
|
+
if (!/\.[a-z0-9]{1,6}$/i.test(rel.split('/').pop() || ''))
|
|
27
|
+
rel += '.bin';
|
|
28
|
+
return path.join(assetsDir, rel);
|
|
29
|
+
}
|
|
30
|
+
function relHref(fromFile, toFile) {
|
|
31
|
+
let r = path.relative(path.dirname(fromFile), toFile).replace(/\\/g, '/');
|
|
32
|
+
if (!r.startsWith('.'))
|
|
33
|
+
r = './' + r;
|
|
34
|
+
return r;
|
|
35
|
+
}
|
|
36
|
+
function extractRefs(html) {
|
|
37
|
+
const out = new Set();
|
|
38
|
+
for (const m of html.matchAll(/(?:href|src)\s*=\s*["']([^"']+)["']/gi))
|
|
39
|
+
out.add(m[1]);
|
|
40
|
+
for (const m of html.matchAll(/srcset\s*=\s*["']([^"']+)["']/gi))
|
|
41
|
+
for (const part of m[1].split(',')) {
|
|
42
|
+
const u = part.trim().split(/\s+/)[0];
|
|
43
|
+
if (u)
|
|
44
|
+
out.add(u);
|
|
45
|
+
}
|
|
46
|
+
return [...out].filter((r) => r && !/^(data:|javascript:|mailto:|tel:|#)/i.test(r));
|
|
47
|
+
}
|
|
48
|
+
export async function cloneSite(startUrl, outDir, opts = {}) {
|
|
49
|
+
const maxPages = Math.min(Math.max(opts.maxPages ?? 30, 1), 200);
|
|
50
|
+
if (!/^https?:\/\//i.test(startUrl))
|
|
51
|
+
startUrl = 'https://' + startUrl;
|
|
52
|
+
const start = new URL(startUrl);
|
|
53
|
+
const host = start.host;
|
|
54
|
+
const root = path.resolve(outDir);
|
|
55
|
+
const assetsDir = path.join(root, '_assets');
|
|
56
|
+
fs.mkdirSync(root, { recursive: true });
|
|
57
|
+
const visited = new Set();
|
|
58
|
+
const queue = [start.href.replace(/#.*$/, '')];
|
|
59
|
+
const pageMap = new Map(); // sayfa absUrl → yerel dosya
|
|
60
|
+
const assetCache = new Map(); // assetUrl → yerel dosya (veya null=başarısız)
|
|
61
|
+
const rawPages = [];
|
|
62
|
+
const errors = [];
|
|
63
|
+
async function getAsset(absUrl) {
|
|
64
|
+
if (assetCache.has(absUrl))
|
|
65
|
+
return assetCache.get(absUrl);
|
|
66
|
+
assetCache.set(absUrl, null);
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch(absUrl, { headers: UA, signal: AbortSignal.timeout(15000) });
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
errors.push(`asset ${res.status} ${absUrl}`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const u = new URL(absUrl);
|
|
74
|
+
const dest = assetFile(u, assetsDir);
|
|
75
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
76
|
+
const ct = res.headers.get('content-type') || '';
|
|
77
|
+
if (/css/i.test(ct) || /\.css(\?|$)/i.test(absUrl)) {
|
|
78
|
+
let css = await res.text();
|
|
79
|
+
css = await rewriteCss(css, absUrl, dest);
|
|
80
|
+
fs.writeFileSync(dest, css);
|
|
81
|
+
}
|
|
82
|
+
else if (/text|javascript|json|xml|svg/i.test(ct) || /\.(js|mjs|json|xml|svg)(\?|$)/i.test(absUrl)) {
|
|
83
|
+
fs.writeFileSync(dest, await res.text());
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
fs.writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
|
|
87
|
+
}
|
|
88
|
+
assetCache.set(absUrl, dest);
|
|
89
|
+
return dest;
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
errors.push(`asset ${absUrl}: ${e.message}`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function rewriteCss(css, cssUrl, cssFile) {
|
|
97
|
+
const refs = [...css.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/gi)];
|
|
98
|
+
for (const m of refs) {
|
|
99
|
+
const ref = m[1];
|
|
100
|
+
if (/^(data:|#)/i.test(ref))
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
const abs = new URL(ref, cssUrl).href;
|
|
104
|
+
const f = await getAsset(abs);
|
|
105
|
+
if (f)
|
|
106
|
+
css = css.split(m[0]).join(`url("${relHref(cssFile, f)}")`);
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
}
|
|
110
|
+
return css;
|
|
111
|
+
}
|
|
112
|
+
// ── PASS 1: sayfaları gez, asset'leri indir ──
|
|
113
|
+
while (queue.length && rawPages.length < maxPages) {
|
|
114
|
+
const cur = queue.shift();
|
|
115
|
+
if (visited.has(cur))
|
|
116
|
+
continue;
|
|
117
|
+
visited.add(cur);
|
|
118
|
+
let html;
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch(cur, { headers: UA, signal: AbortSignal.timeout(20000) });
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
errors.push(`page ${res.status} ${cur}`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!/text\/html/i.test(res.headers.get('content-type') || ''))
|
|
126
|
+
continue;
|
|
127
|
+
html = await res.text();
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
errors.push(`page ${cur}: ${e.message}`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const u = new URL(cur);
|
|
134
|
+
const file = pageFile(u, root);
|
|
135
|
+
pageMap.set(cur, file);
|
|
136
|
+
rawPages.push({ url: u, file, html });
|
|
137
|
+
// linkleri+asset'leri çıkar
|
|
138
|
+
for (const ref of extractRefs(html)) {
|
|
139
|
+
let abs;
|
|
140
|
+
try {
|
|
141
|
+
abs = new URL(ref, cur);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
abs.hash = '';
|
|
147
|
+
const href = abs.href;
|
|
148
|
+
const isAsset = ASSET_EXT.test(abs.pathname);
|
|
149
|
+
if (isAsset) {
|
|
150
|
+
await getAsset(href);
|
|
151
|
+
}
|
|
152
|
+
else if (abs.host === host && (PAGE_EXT.test(abs.pathname) || !/\.[a-z0-9]{1,6}$/i.test(abs.pathname.split('/').pop() || ''))) {
|
|
153
|
+
if (!visited.has(href) && rawPages.length + queue.length < maxPages)
|
|
154
|
+
queue.push(href);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ── PASS 2: her sayfanın HTML'inde asset + iç-link yollarını yerele düzelt, yaz ──
|
|
159
|
+
for (const pg of rawPages) {
|
|
160
|
+
let html = pg.html;
|
|
161
|
+
for (const ref of extractRefs(html)) {
|
|
162
|
+
let abs;
|
|
163
|
+
try {
|
|
164
|
+
abs = new URL(ref, pg.url.href);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const bare = abs.href.replace(/#.*$/, '');
|
|
170
|
+
let local = null;
|
|
171
|
+
if (assetCache.get(bare))
|
|
172
|
+
local = assetCache.get(bare);
|
|
173
|
+
else if (pageMap.has(bare))
|
|
174
|
+
local = pageMap.get(bare);
|
|
175
|
+
if (local) {
|
|
176
|
+
const rel = relHref(pg.file, local);
|
|
177
|
+
html = html.split(`"${ref}"`).join(`"${rel}"`).split(`'${ref}'`).join(`'${rel}'`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
fs.mkdirSync(path.dirname(pg.file), { recursive: true });
|
|
181
|
+
fs.writeFileSync(pg.file, html);
|
|
182
|
+
}
|
|
183
|
+
return { pages: rawPages.length, assets: [...assetCache.values()].filter(Boolean).length, outDir: root, pageFiles: rawPages.map((p) => p.file), errors: errors.slice(0, 30) };
|
|
184
|
+
}
|
package/dist/theme.js
CHANGED
package/dist/tools.js
CHANGED
|
@@ -19,6 +19,7 @@ import { getExcludedTools } from './extensions.js';
|
|
|
19
19
|
import { checkCommand, isShellCommandReadOnly } from './cmdsec.js';
|
|
20
20
|
import * as Diff from 'diff';
|
|
21
21
|
import * as computer from './computer.js';
|
|
22
|
+
import { cloneSite } from './clone.js';
|
|
22
23
|
// Agent/alt-agent araçlarının backend'e ulaşması için config. cli.tsx başlangıçta
|
|
23
24
|
// setToolConfig ile aynı (mutable) config nesnesini verir → /config değişiklikleri görülür.
|
|
24
25
|
let toolConfig = null;
|
|
@@ -443,6 +444,25 @@ export const toolSchemas = [
|
|
|
443
444
|
},
|
|
444
445
|
},
|
|
445
446
|
},
|
|
447
|
+
{
|
|
448
|
+
type: 'function',
|
|
449
|
+
function: {
|
|
450
|
+
name: 'CloneSite',
|
|
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
|
+
'WebFetch sadece metin döndürür, klonlama için işe yaramaz). İç linkleri takip eder (aynı host, sayfa limitli), ' +
|
|
454
|
+
'tüm asset yollarını yerele düzeltir → çıktı tarayıcıda offline açılır. Bittiğinde kaç sayfa/asset indiğini özetle.',
|
|
455
|
+
parameters: {
|
|
456
|
+
type: 'object',
|
|
457
|
+
properties: {
|
|
458
|
+
url: { type: 'string', description: 'Klonlanacak sitenin başlangıç URL\'i (örn. https://site.com)' },
|
|
459
|
+
out_dir: { type: 'string', description: 'Çıktı klasörü (örn. ./site-klon). Yoksa oluşturulur.' },
|
|
460
|
+
max_pages: { type: 'number', description: 'En fazla kaç sayfa gezilsin (varsayılan 30, üst sınır 200). Tek sayfa için 1.' },
|
|
461
|
+
},
|
|
462
|
+
required: ['url', 'out_dir'],
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
},
|
|
446
466
|
{
|
|
447
467
|
type: 'function',
|
|
448
468
|
function: {
|
|
@@ -685,7 +705,7 @@ const computerToolSchemas = [
|
|
|
685
705
|
// çağırıp işi yapar, planı ekrana basmaz. (Gerekirse buraya yeni araç eklenir.)
|
|
686
706
|
const CORE_TOOLS = new Set([
|
|
687
707
|
'Bash', 'PowerShell', 'Read', 'Write', 'Edit', 'Glob', 'Grep',
|
|
688
|
-
'WebFetch', 'WebSearch', 'Sleep', 'SecurityScan', 'CodeAudit', 'JwtAudit', 'CreateDocument',
|
|
708
|
+
'WebFetch', 'WebSearch', 'CloneSite', 'Sleep', 'SecurityScan', 'CodeAudit', 'JwtAudit', 'CreateDocument',
|
|
689
709
|
'TaskOutput', // uzun komutları arka planda çalıştırıp bitişini yoklamak için (run_in_background)
|
|
690
710
|
'AskUserQuestion', // GERİ EKLENDİ: FLAT şema (options: string[]) ile 32B temiz çağrı üretiyor
|
|
691
711
|
// (test edildi). Eski degenere NESTED şemadandı ([{label,description}]). Handler string→{label}
|
|
@@ -717,6 +737,7 @@ const TOOL_META = {
|
|
|
717
737
|
Glob: { readOnly: true, concurrencySafe: true },
|
|
718
738
|
Grep: { readOnly: true, concurrencySafe: true },
|
|
719
739
|
WebFetch: { readOnly: true, validate: (a) => (a && a.url ? null : 'url gerekli') },
|
|
740
|
+
CloneSite: { needsPermission: true, validate: (a) => (a && a.url && a.out_dir ? null : 'url ve out_dir gerekli') },
|
|
720
741
|
TaskOutput: { readOnly: true, concurrencySafe: true },
|
|
721
742
|
Bash: { needsPermission: true, validate: (a) => (a && a.command ? null : 'command gerekli') },
|
|
722
743
|
Write: { needsPermission: true, validate: (a) => (a && a.file_path ? null : 'file_path gerekli') },
|
|
@@ -1231,6 +1252,13 @@ export function toolLabel(name, args) {
|
|
|
1231
1252
|
catch {
|
|
1232
1253
|
return String(args.url).slice(0, 50);
|
|
1233
1254
|
} })()}`;
|
|
1255
|
+
if (name === 'CloneSite')
|
|
1256
|
+
return `🧬 Site klonlanıyor: ${(() => { try {
|
|
1257
|
+
return new URL(/^https?:/.test(args.url) ? args.url : 'https://' + args.url).hostname;
|
|
1258
|
+
}
|
|
1259
|
+
catch {
|
|
1260
|
+
return String(args.url).slice(0, 40);
|
|
1261
|
+
} })()} → ${args.out_dir}`;
|
|
1234
1262
|
if (name === 'Agent')
|
|
1235
1263
|
return `Agent(${args.description || ''}${args.subagent_type ? ':' + args.subagent_type : ''}${args.run_in_background ? ', bg' : ''})`;
|
|
1236
1264
|
if (name === 'TaskOutput')
|
|
@@ -1737,6 +1765,28 @@ export async function executeTool(name, args) {
|
|
|
1737
1765
|
_webFetchCache.set(url, { text: txt, t: Date.now() });
|
|
1738
1766
|
return { ok: true, output: header + (txt || '(empty)') };
|
|
1739
1767
|
}
|
|
1768
|
+
if (name === 'CloneSite') {
|
|
1769
|
+
const url = String(args.url || '');
|
|
1770
|
+
const outDir = String(args.out_dir || '');
|
|
1771
|
+
if (!url || !outDir)
|
|
1772
|
+
return { ok: false, output: 'url ve out_dir gerekli' };
|
|
1773
|
+
const maxPages = Number(args.max_pages) || 30;
|
|
1774
|
+
try {
|
|
1775
|
+
const r = await cloneSite(url, outDir, { maxPages });
|
|
1776
|
+
const lines = [
|
|
1777
|
+
`✓ Klon tamam: ${r.pages} sayfa + ${r.assets} asset indirildi → ${r.outDir}`,
|
|
1778
|
+
`Aç: ${path.join(r.outDir, 'index.html')} (tarayıcıda offline açılır, asset yolları yerele düzeltildi)`,
|
|
1779
|
+
];
|
|
1780
|
+
if (r.pageFiles.length)
|
|
1781
|
+
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
|
+
if (r.errors.length)
|
|
1783
|
+
lines.push(`Atlanan/hatalı ${r.errors.length} kaynak (ölü link/erişim) — klon yine de çalışır.`);
|
|
1784
|
+
return { ok: true, output: lines.join('\n') };
|
|
1785
|
+
}
|
|
1786
|
+
catch (e) {
|
|
1787
|
+
return { ok: false, output: 'Klonlama hatası: ' + (e?.message || String(e)) };
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1740
1790
|
if (name === 'WebSearch') {
|
|
1741
1791
|
// Once gateway arama proxy'sini dene (Tavily anahtari sunucuda ayarliysa onu kullanir; anahtar istemciye gelmez)
|
|
1742
1792
|
try {
|