clone-website
Klone jede Live-Website 1:1 (Layout, Typografie, Assets, Scroll-Animationen, zustandsbehaftete Interaktionen) ausgehend von nichts als ihrer URL und beweise die Treue mit einem bewerteten Benchmark anstatt sie nur zu behaupten.
Laufzeitabhängigkeit und Start
Jeder Live-Seiten-Vorgang wird als ego-browser nodejs <<'EOF' ... EOF in Bash ausgeführt, unter Verwendung der vorinstallierten Fassaden page, browser und taskSpaces des ego lite Runtimes.
Die Laufzeit ist nicht austauschbar, da die Methode auf drei Arten von ihr abhängt. Die Schleife auf Pixelebene benötigt die identische Sonde, die in der identischen Laufzeit auf Original und Klon ausgeführt wird, sonst sind die Diff-Zahlen bedeutungslos. Die Korrekturschleife benötigt Aufgabenbereiche: Der Agent tastet in seinen eigenen isolierten Bereichen, während der Benutzer gleichzeitig das Original und den Klon in seinen Bereichen durchsucht, kann jeden Bereich für eine visuelle Überprüfung übernehmen und ihn zurückgeben, ohne dass eine Seite um den Browser konkurriert. Und jedes Sondenskript und jeder Vorbehalt in dieser Fertigkeit ist am gemessenen Verhalten dieser Laufzeit kalibriert. Deshalb führt eine fehlende Laufzeit zur Installation von ego lite und nicht zu einem Ersatz.
- Wenn die
ego-browser-Fertigkeit in dieser Umgebung installiert ist, lade sie vor Phase 0 und befolge alle ihre Korrektheitsregeln. Diese Fertigkeit ist ein Wrapper darum; bei Konflikten gewinnen die Regeln der ego-browser-Fertigkeit für die Browser-Mechanik, die Regeln dieser Fertigkeit gewinnen für den Klon-Workflow. - Wenn nur diese Fertigkeit vorhanden ist, funktioniert sie eigenständig. Der Vertrag ist das
ego-browser-CLI (bereitgestellt von der ego lite App). In einer neuen oder unbekannten Umgebung überprüfe einmal mitcommand -v ego-browservor Phase 0. - Wenn das CLI fehlt, improvisiere nicht. Stoppe, lies
references/install.mdund führe den Benutzer durch die Installation von ego lite (Download überscripts/install-ego-browser.sh, dann schließt der Benutzer das Onboarding in der App-GUI ab, die das CLI registriert; warte auf ihre Bestätigung, überprüfe mitcommand -v ego-browser, dann fahre fort). Falle niemals auf Playwright, einen anderen Browser, WebFetch oder einen anderen Automatisierungspfad zurück; Sondenergebnisse von einer anderen Laufzeit sind nicht vergleichbar und die Umgebungsvorbehalte dieser Fertigkeit gelten nicht mehr. - Nach dem ersten erfolgreichen Befehl gehe davon aus, dass die Laufzeit für den Rest der Sitzung bereit ist; untersuche nur, wenn ein tatsächlicher Befehl einen Fehler verursacht.
Die Methode in einem Satz: Behandle die Live-Seite als Ground Truth, den du abfragen kannst, nicht als Bild, das du begutachtest. Entnehme das echte DOM, echte CSS-Tokens, echte Keyframes, echte Schriften und echte Konfigurationsdaten aus der laufenden Seite, baue aus diesen Fakten neu auf, führe dann die gleiche Messsonde auf Original und Klon aus und diffe die Zahlen, bis sie konvergieren.
Dateizuordnung: Kopier-Einfüge-Sondenskripte in references/probes.md; Benchmark-Protokoll in references/eval.md; ego lite Installationsanleitung in references/install.md (Installationsprogramm mitgeliefert unter scripts/install-ego-browser.sh); Tool-/Funktions-Designvorlage in references/tool-design-template.md; kanonische Tool-Implementierungen in tools/ (registriert in tools/manifest.json); Bewerter in scripts/eval_score.py; Screenshot-Zerteiler in scripts/slice.py.
Ausführungsmodell
- Ein Aufgabenbereich für den gesamten Auftrag.
taskSpaces.useOrCreate('reverse <site>')am Anfang jedes Heredocs; derselbe Name über alle Aufrufe hinweg. Schließe es nicht zwischen den Sonden ab; schließe es mit{ keep: false }erst nach der endgültigen Annahme ab. Wenn der Benutzer den Bereich übernimmt, ist das ein harter Stopp: Melde den Zustand und warte, übernimm niemals selbsttakeOver. - Stapelweise nach Phase, nicht nach Aktion. Ein Heredoc sollte eine ganze Sonde abschließen (Öffnen, Videos einfrieren, auf jeder Tiefe messen, ausgeben). Teile Aufrufe nur an echten Grenzen auf: Shell-Arbeit zwischen Browser-Arbeit (curl-Downloads, greps, Python), Benutzerbestätigung oder visuelle Inspektion eines gespeicherten Bildes.
- Artefakte in Dateien, Zusammenfassungen nach stdout. Jede Sonde schreibt ihr volles JSON/HTML in
reference/undconsole.logt nur Zählungen und Pfade. Gib niemals ein vollständiges DOM, eine CSS-Datei oder eine Geometrietabelle in die Konversation aus. - Jedes Sondenskript ist in sich geschlossen. Aufgabenbereichsauswahl und
browser.openOrReuseTab(url, { wait: true, timeout: 30000 })jedes Mal oben; der Laufzeitzustand überlebt nicht über Heredocs hinweg. - Nur absolute Pfade innerhalb von Heredocs. Der nodejs-Prozess von ego erbt das aktuelle Arbeitsverzeichnis der Shell nicht; relative
fs.writeFileSync-Pfade verursachen ENOENT. Definiere eineROOT-Konstante oben in jedem Skript.
Schnellstart: Recon-Bootstrap
Ein Aufruf erfasst die Ground Truth, von der ein Wiederaufbau ausgeht. Passe URL und Pfade an; die zusammengesetzte Form ist der Punkt.
mkdir -p reference/{dom,css,js,screenshots} && ego-browser nodejs <<'EOF'
const fs = await import('node:fs')
const ROOT = '/abs/path/to/project' // egos nodejs erbt das Shell-cwd nicht
await taskSpaces.useOrCreate('reverse example.com')
await browser.openOrReuseTab('https://example.com', { wait: true, timeout: 30000 })
// Freeze Autoplay-Videos ZUERST: sie sättigen den Render-Thread und spätere Auswertungen laufen zeitlich aus.
await page.evaluate(() => {
const freeze = () => document.querySelectorAll('video').forEach(v => v.pause())
freeze(); setInterval(freeze, 400)
})
// Bewache den Viewport: w/h von 0 erzeugt stillschweigend leere Screenshots und Müll-Rechtecke.
const info = await page.info()
if (!info.w || !info.h) throw new Error('Viewport ist 0, stelle den echten Tab vor dem Recon wieder her: ' + JSON.stringify(info))
const facts = await page.evaluate(() => ({
html: document.documentElement.outerHTML,
outline: Array.from(document.querySelectorAll('body section, body header, body footer, body main > div'))
.map(el => { const r = el.getBoundingClientRect(); return {
tag: el.tagName, cls: (el.className?.toString() || '').slice(0, 120),
y: Math.round(r.top + scrollY), h: Math.round(r.height), w: Math.round(r.width) } })
.filter(s => s.h > 40).sort((a, b) => a.y - b.y),
cssUrls: performance.getEntriesByType('resource').map(e => e.name).filter(u => /\.css(\?|$)/.test(u)),
fontFiles: performance.getEntriesByType('resource').map(e => e.name).filter(u => /\.(woff2?|ttf|otf)(\?|$)/.test(u)),
jsChunks: performance.getEntriesByType('resource').map(e => e.name).filter(u => /\.js(\?|$)/.test(u)),
}))
fs.writeFileSync(ROOT + '/reference/dom/full-page.html', facts.html)
fs.writeFileSync(ROOT + '/reference/dom/outline.json', JSON.stringify(facts.outline, null, 2))
fs.writeFileSync(ROOT + '/reference/urls.json', JSON.stringify({ css: facts.cssUrls, fonts: facts.fontFiles, js: facts.jsChunks }, null, 2))
await page.evaluate(() => scrollTo(0, 0))
await page.waitForTimeout(800)
await page.screenshot({ path: ROOT + '/reference/screenshots/original-full.png', fullPage: true })
console.log(JSON.stringify({ sections: facts.outline.length, cssFiles: facts.cssUrls.length,
fontFiles: facts.fontFiles.length, jsChunks: facts.jsChunks.length, htmlKB: Math.round(facts.html.length / 1024) }))
EOF
Dann Shell-Arbeit: Lade jede CSS/Schrift/JS-URL mit curl in reference/ herunter und zerteile den Screenshot pro Abschnitt mit python3 scripts/slice.py reference/screenshots/original-full.png reference/dom/outline.json reference/screenshots/original/.
Workflow
Phase 0, Einrichtung
Projektverzeichnis mit reference/{dom,css,js,screenshots}/ für Recon-Artefakte (niemals ausgeliefert, von jedem Repo-Push ausgeschlossen); Entwicklungs-Server auf einem festen Port im Hintergrund, damit der Benutzer kontinuierlich überprüfen kann. Gerüst Next.js manuell, wenn public/ bereits heruntergeladene Assets enthält; create-next-app überschreibt das Verzeichnis.
Vor dem Ausliefern, lsof -iTCP:<port> -sTCP:LISTEN (ein übrig gebliebener Server von einem vorherigen Klon-Job antwortet überzeugend auf Ihrem Port), und vor jeder Sonde des Klons, überprüfe, dass document.title übereinstimmt; eine plausible curl-Antwort ist kein Beweis dafür, dass der Browser-Tab Ihre Seite anzeigt.
Phase 1, statische Erfassung
Führe den Recon-Bootstrap aus, fülle dann die Lücken (vollständige Skripte in references/probes.md):
- Design-Tokens: Durchsuche die heruntergeladene CSS mit grep nach
--brand-*,--font-size-*,--color-*, Abstandsskalen. Lies Größen und Farben aus Tokens, schätze sie niemals aus Screenshots. Ein Token, das du erwartest, aber nicht finden kannst, befindet sich normalerweise in einer anderen CSS-Datei; durchsuche alle, bevor du schlussfolgerst, dass es nicht existiert. - Schriftarten:
@font-facewird oft von JS zur Laufzeit injiziert, daher beweist das Durchsuchen des gespeicherten HTMLs nichts. Kombiniere auf der Live-Seite drei Quellen:performance.getEntriesByType('resource')gefiltert nach Schrifterweiterungen,document.fontsfür Familie/Gewicht/Status und eindocument.styleSheets-Walk fürCSSFontFaceRule, um Familie auf Gewicht auf src abzubilden. Lade mitcurl --globoffherunter (Dateinamen in eckigen Klammern brechen curl-Globbing). Hoste selbst inpublic/fonts/. - Assets: Zähle
img[src],srcset,video src, Hintergrundbild-URLs, Inline-SVGs auf. Dekodiere/_next/image?url=...zurück zu ursprünglichen Pfaden. Spiegele ursprüngliche Pfade unterpublic/. Extrahiere Inline-SVGs wörtlich, zeichne sie nicht neu. - Mock-/Produkt-UI-Inhalt: Dashboards, Editoren und Kartenpanels erhalten ihren Inhalt aus Konfigurationsobjekten in kompilierten JS-Blöcken. Durchsuche die heruntergeladenen Blöcke mit grep nach einem sichtbaren String; das Konfigurationsobjekt neben dem Treffer enthält den vollständigen Datensatz (alle Karussell-Varianten, alle Zeilen), weitaus zuverlässiger als Screenshots, die den lazy-loading Zustand übersehen könnten.
Referenz-Screenshots folgen den Korrektheitsregeln unten: eine fullPage-Erfassung bei Scroll 0, pro Abschnitt mit scripts/slice.py zerteilt, Videos zuerst eingefroren, zustandsbehaftete Balken zuerst zurückgesetzt.
Phase 2, Bewegungs- und Interaktions-Rückwärtsentwicklung
Arbeite zuerst die günstigste ab; stoppe, sobald der Effekt erklärt ist:
- CSS-Statikanalyse (kostenlos, bereits heruntergeladen): Durchsuche mit grep nach
animation-timeline|view-timeline|scroll-timeline|@keyframes. Ein Treffer aufanimation-timeline: view()bedeutet native CSS-Scroll-gesteuerte Animationen: kopiere Keyframes und Bereiche wörtlich, kein JS nötig. - Laufzeitinventar:
document.getAnimations({ subtree: true }), der Ground Truth. Pro Animation:effect.getKeyframes(),timeline.constructor.name(ViewTimelineist scroll-gesteuert,DocumentTimelineist zeitgesteuert),rangeStart/rangeEnd, Zielelement. Pro-Element-Parameter wie--parallax-from: 6%leben auf Inline-Stilen. - MutationObserver plus echtes Rad-Scrollen für JS-ausgelöste Effekte:
attributeFilter: ['class','style','src'], scrolle die gesamte Seite mitpage.mouse.wheel, protokolliere Mutationen. Null Klassenmutationen über einen vollständigen Scroll hinweg beweist, dass die Effekte reines CSS sind, was den Wiederaufbau massiv vereinfacht. - Mehrtiefen-Zustandsabtastung für Fixed/Sticky-Zustandsautomaten (schwebende Eingabeleisten, einklappende Kopfzeilen): Mache eine Momentaufnahme der gesamten Unterbaum des Elements (jedes Kind-Rechteck, Opazität, Radius, Hintergrund, Schatten) bei mehreren Scrolltiefen in beide Richtungen, diffe dann die Momentaufnahmen zwischen den Zuständen. Nur Positionsprüfungen übersehen Formveränderungen; der Zustandsübergang ist die Interaktion.
Phase 3, Wiederaufbau
Wähle den Wiederaufbau-Pfad zuerst nach Seitentyp. Statisch exportierte Seiten (Webflow, Framer: data-wf-site-Attribut, website-files.com/framerusercontent.com CDN) klonen mit höchster Treue als Roh-HTML-Spiegel, nicht als Komponenten-Wiederaufbau: Lade das Pre-Hydrations-Server-HTML mit curl als Gerüst (das erfasste Live-DOM ist bereits hydriert – Karussell-Klone eingefügt, SplitText-Zeichen geteilt – und doppelinitialisiert, wenn Skripte erneut ausgeführt werden), schreibe jede CDN-URL zu selbst gehosteten Kopien um, entferne Analyse-/Einwilligungsskripte, behalte die funktionale Laufzeit (Site-JS, GSAP, Karussell-Bibliotheken) selbst gehostet. Vier Regeln für diesen Pfad:
- Assets können sich über mehrere CDN-Ordner erstrecken (gemeinsame Medienbibliotheken); schreibe jede
cdn-host/<order>/<datei>nach Basisnamen um, nicht nach dem Hauptordner-Präfix der Seite. - Entferne
integrity/crossoriginvon jedem umgeschriebenen Ressourcen-Tag: Der SRI-Hash wurde gegen die ursprünglichen Bytes berechnet, und der Browser lehnt die modifizierte Datei stillschweigend ab – das Symptom ist eine ungestylte Seite (Dokumentenhöhe explodiert,document.fontsleer) ohne sondensichtbaren Fehler. - Die Entfernung von Tracking-Skripten durch eine Keyword-Blockliste tötet funktionale Skripte, die lediglich eine Analyse-API mit Graceful Fallback erwähnen (ein Head-Skript, das den Textwechsel der OS-Erkennung der Download-Buttons besitzt, referenzierte PostHog); überprüfe die Liste der gelöschten Skripte und whiteliste die funktionalen.
- Nichtdeterministische Experiment-Skripte (
Math.random()Traffic-Splits, Redirect-Canaries) bleiben entfernt, auch wenn sie funktional sind; sie machen Benchmark-Läufe nicht wiederholbar.
Für JS-Framework-Seiten (Next.js, Remix – Hydrationskonflikt macht einen Spiegel undurchführbar), baue komponentenweise wieder auf:
- Eine Komponente pro Top-Level-Abschnitt, benannt nach dem geschäftlichen Inhalt des Abschnitts.
- Tokens aus Phase 1 kommen unverändert in
globals.css; Schriften selbst gehostet mit der echten Gewichtszuordnung. - Spiegele die ursprüngliche DOM-Mounting-Strategie, nicht nur das, was sichtbar ist: Originale behalten alle Karussell-Frames, Rotationsvarianten und Avatar-Wände montiert (versteckt), während ein bedingt rendernder Klon die gleichen Pixel mit einem Bruchteil der Elemente zeigt und dann bei den Medienzählungen pro Abschnitt im Benchmark scheitert. Gleiche Elementzahlen pro Abschnitt mit dem DOM des Originals ab, nicht mit seinem Screenshot.
- Repliziere die Container-Hierarchie des Originals, nicht nur seine Optik.
stickybeschränkt sich auf sein übergeordnetes Element: Eine Nav, die bis zum Footer überleben muss, braucht ein seitenumspannendes übergeordnetes Element; ein Vorhang-Enthüllungspanel braucht ein übergeordnetes Element, das dort beginnt, wo der Effekt beginnt, sonst fixiert es sich ab Bildschirm eins. Wenn ein Abschnitt nach der Umstrukturierung bricht, vermute zuerst eine Änderung des sticky- oder Stacking-Context-Bereichs. - Ansichtsfenster-fixierte Elemente (schwebende Chat-Leisten) sind in gut gebauten Originalen body-level fixierte Container: Verwende ein React-Portal, niemals
absoluteinnerhalb eines Abschnitts. - Einklappende Kopfzeilen, die die Dokumentenhöhe ändern, bekämpfen das Chrome Scroll Anchoring und zittern. Robuste Architektur: Ein Platzhalter mit konstanter Höhe im Fluss (Dokumentenhöhe ändert sich nie) mit der inneren Kopfzeile, die zu
fixedwechselt, plus einer Slide-In-Animation, sobald sie vollständig herausgescrollt ist. Wenn Höhe animiert werden muss, fügeoverflow-anchor: noneund Hysterese zwischen Einklapp- und Ausklapp-Schwellenwerten hinzu. - Dekorative gezackte Kanten (Muschel-/Stempel-Ränder): solides seitliches SVG, das in Richtung der Lücke mit 1px Überlappung in den Nachbarn zeigt
…
[gekürzt für die Übersetzung]