clone-website
任意のライブウェブサイトを、URLのみから1:1(レイアウト、タイポグラフィ、アセット、スクロールアニメーション、状態を持つインタラクション)で複製し、忠実度を主張ではなくスコア付きベンチマークで証明します。
ランタイム依存関係とブートストラップ
すべてのライブページ操作は、Bash内でego-browser nodejs <<'EOF' ... EOFとして実行され、ego liteランタイムのプリロードされたpage、browser、taskSpacesファサードを使用します。
ランタイムは交換可能ではありません。なぜなら、メソッドは3つの点でランタイムに依存しているからです。ピクセルレベルのループでは、オリジナルとクローンで同一のプローブを同一のランタイムで実行する必要があり、そうでなければ差分の数値は意味をなしません。修正ループではタスクスペースが必要です。エージェントは自身の独立したスペースでプローブを実行している間、ユーザーは自身のスペースでオリジナルとクローンを同時にブラウズし、いつでも任意のスペースを引き継いで視覚チェックを行い、戻すことができ、両者がブラウザを奪い合うことはありません。そして、このスキルのすべてのプローブスクリプトと注意事項は、このランタイムの測定された動作に合わせて調整されています。これが、ランタイムがない場合に代替手段ではなくego liteのインストールに誘導する理由です。
- この環境に
ego-browserスキルがインストールされている場合、フェーズ0の前にそれをロードし、そのすべての正確性ルールに従います。 このスキルはそのラッパーです。競合がある場合、ブラウザのメカニズムについてはego-browserスキルのルールが優先され、クローンワークフローについてはこのスキルのルールが優先されます。 - このスキルのみが存在する場合、スタンドアロンで動作します。 契約は
ego-browserCLI(ego liteアプリによって提供される)です。新しい環境または未知の環境では、フェーズ0の前にcommand -v ego-browserで一度確認します。 - CLIがない場合、アドリブは行いません。 停止し、
references/install.mdを読み、ユーザーにego liteのインストール手順を案内します(scripts/install-ego-browser.shを介してダウンロード後、ユーザーがアプリGUIでオンボーディングを完了し、CLIを登録します。ユーザーの確認を待ち、command -v ego-browserで検証してから再開します)。Playwright、他のブラウザ、WebFetch、またはその他の自動化パスにフォールバックしてはいけません。別のランタイムからのプローブ結果は比較できず、このスキルの環境注意事項も適用されなくなります。 - 最初の成功したコマンドの後、ランタイムはセッションの残りの部分で準備ができていると想定し、実際のコマンドがエラーになった場合のみ調査します。
メソッドを一言で言うと:ライブサイトを、目視で確認する画像ではなく、クエリできるグラウンドトゥルースとして扱います。実行中のページから実際のDOM、実際のCSSトークン、実際のキーフレーム、実際のフォント、実際の設定データを抽出し、それらの事実から再構築し、オリジナルとクローンで同じ測定プローブを実行して数値が収束するまで差分を取ります。
ファイルマップ:プローブスクリプトのコピー&ペーストはreferences/probes.md、ベンチマークプロトコルはreferences/eval.md、ego liteインストールガイドはreferences/install.md(インストーラはscripts/install-ego-browser.shに同梱)、ツール/関数設計テンプレートはreferences/tool-design-template.md、標準ツール実装はtools/(tools/manifest.jsonに登録)、スコアラーはscripts/eval_score.py、スクリーンショットスライサーはscripts/slice.py。
実行モデル
- ジョブ全体で1つのタスクスペース。 すべてのヒアドキュメントの先頭で
taskSpaces.useOrCreate('reverse <site>')。すべての呼び出しで同じ名前を使用します。プローブ間でcompleteしてはいけません。最終承認後にのみ{ keep: false }で完了します。ユーザーがスペースを引き継いだ場合、それはハードストップです:状態を報告して待機し、自分でtakeOverしてはいけません。 - アクションごとではなく、フェーズごとにバッチ処理。 1つのヒアドキュメントでプローブ全体(開く、ビデオをフリーズ、各深さで測定、出力)を完了する必要があります。分割呼び出しは実際の境界(シェル作業とブラウザ作業の間、curlダウンロード、grep、Python、ユーザー確認、または保存された画像の視覚検査)でのみ行います。
- アーティファクトはファイルに、サマリーはstdoutに。 すべてのプローブは完全なJSON/HTMLを
reference/に書き込み、カウントとパスのみをconsole.logします。完全なDOM、CSSファイル、またはジオメトリテーブルを会話にダンプしてはいけません。 - すべてのプローブスクリプトは自己完結型。 毎回、タスクスペースの選択と
browser.openOrReuseTab(url, { wait: true, timeout: 30000 })を先頭に配置します。ランタイム状態はヒアドキュメント間で保持されません。 - ヒアドキュメント内では絶対パスのみ。 egoのnodejsプロセスはシェルのカレントワーキングディレクトリを継承しません。相対
fs.writeFileSyncパスはENOENTになります。すべてのスクリプトの先頭でROOT定数を定義します。
クイックスタート:再コンボットストラップ
1回の呼び出しで、再構築の出発点となるグラウンドトゥルースをキャプチャします。URLとパスを適応させます。複合的な形状が重要です。
mkdir -p reference/{dom,css,js,screenshots} && ego-browser nodejs <<'EOF'
const fs = await import('node:fs')
const ROOT = '/abs/path/to/project' // egoのnodejsはシェルのcwdを継承しません
await taskSpaces.useOrCreate('reverse example.com')
await browser.openOrReuseTab('https://example.com', { wait: true, timeout: 30000 })
// 最初に自動再生ビデオをフリーズ:それらはレンダースレッドを飽和させ、後の評価がタイムアウトします。
await page.evaluate(() => {
const freeze = () => document.querySelectorAll('video').forEach(v => v.pause())
freeze(); setInterval(freeze, 400)
})
// ビューポートをガード:w/hが0の場合、サイレントに空のスクリーンショットと矩形のゴミが生成されます。
const info = await page.info()
if (!info.w || !info.h) throw new Error('ビューポートが0です。再コンボットの前に実際のタブを復元してください:' + 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
次にシェルワーク:すべてのCSS/フォント/JS URLをreference/にcurlでダウンロードし、python3 scripts/slice.py reference/screenshots/original-full.png reference/dom/outline.json reference/screenshots/original/でセクションごとにスクリーンショットをスライスします。
ワークフロー
フェーズ0、セットアップ
再コンボットアーティファクト用のreference/{dom,css,js,screenshots}/を持つプロジェクトディレクトリ(出荷不可、リポジトリプッシュから除外)。ユーザーが継続的に確認できるように、バックグラウンドで固定ポートに開発サーバーを起動します。public/にダウンロード済みアセットがすでにある場合は手動でNext.jsをスキャフォールドします。create-next-appはディレクトリを上書きします。
サーバーを起動する前に、lsof -iTCP:<port> -sTCP:LISTENを実行します(以前のクローンジョブの残骸サーバーがあなたのポートに説得力のある応答を返す可能性があります)。また、クローンのプローブの前に、document.titleが一致することをアサートします。もっともらしいcurl応答は、ブラウザタブがあなたのページを表示していることの証明にはなりません。
フェーズ1、静的キャプチャ
再コンボットブートストラップを実行し、ギャップを埋めます(完全なスクリプトはreferences/probes.mdにあります):
- デザイントークン:ダウンロードしたCSSを
--brand-*、--font-size-*、--color-*、スペーシングスケールでgrepします。サイズと色はトークンから読み取り、スクリーンショットから推定してはいけません。予想されるが見つからないトークンは、通常別のCSSファイルにあります。存在しないと結論付ける前に、すべてのファイルをgrepします。 - フォント:
@font-faceは実行時にJSによって注入されることが多いため、保存されたHTMLをgrepしても何も証明されません。ライブページで3つのソースを組み合わせます:フォント拡張子にフィルタリングされたperformance.getEntriesByType('resource')、ファミリー/ウェイト/ステータス用のdocument.fonts、ファミリーをウェイトにsrcにマッピングするためのCSSFontFaceRule用のdocument.styleSheetsウォーク。curl --globoff(ブラケット付きのファイル名はcurlのグロビングを壊します)でダウンロードします。public/fonts/にセルフホストします。 - アセット:
img[src]、srcset、video src、background-image URL、インラインSVGを列挙します。/_next/image?url=...を元のパスにデコードします。元のパスをpublic/の下にミラーリングします。インラインSVGはそのまま抽出し、再描画してはいけません。 - モック/プロダクトUIコンテンツ:ダッシュボード、エディタ、カードパネルは、コンパイルされたJSチャンク内の設定オブジェクトからコンテンツを取得します。ダウンロードしたチャンクを可視文字列でgrepします。ヒットの隣にある設定オブジェクトは、完全なデータセット(すべてのカルーセルバリアント、すべての行)を保持しており、レイジーロードされた状態を見逃すスクリーンショットよりもはるかに信頼性が高くなります。
参照スクリーンショットは以下の正確性ルールに従います:スクロール0で1回のfullPageキャプチャをscripts/slice.pyでセクションごとにスライスし、ビデオを最初にフリーズし、ステートフルバーを最初にリセットします。
フェーズ2、モーションとインタラクションのリバースエンジニアリング
最もコストの低いものから順に作業し、効果が説明できたらすぐに停止します:
- CSS静的解析(無料、すでにダウンロード済み):
animation-timeline|view-timeline|scroll-timeline|@keyframesをgrepします。animation-timeline: view()のヒットはネイティブCSSスクロール駆動アニメーションを意味します:キーフレームと範囲をそのままコピーし、JSは不要です。 - ランタイムインベントリ:
document.getAnimations({ subtree: true })、これがグラウンドトゥルースです。アニメーションごとに:effect.getKeyframes()、timeline.constructor.name(ViewTimelineはスクロール駆動、DocumentTimelineは時間駆動)、rangeStart/rangeEnd、ターゲット要素。--parallax-from: 6%のような要素固有のパラメータはインラインスタイルにあります。 - JSトリガー効果用のMutationObserverと実際のホイールスクロール:
attributeFilter: ['class','style','src']、page.mouse.wheelでページ全体をスクロールし、ミューテーションをログに記録します。完全なスクロール全体でクラスミューテーションがゼロの場合、効果が純粋なCSSであることが証明され、再構築が大幅に簡素化されます。 - 固定/スティッキー状態マシン用のマルチ深度状態サンプリング(フローティング入力バー、折りたたみヘッダー):いくつかのスクロール深度で両方向に要素のサブツリー全体(すべての子のrect、不透明度、半径、背景、影)のスナップショットを取得し、状態間でスナップショットを差分します。位置のみのチェックは形状変化を見逃します。状態遷移がインタラクションです。
フェーズ3、再構築
最初にサイトタイプに基づいて再構築ルートを選択します。 静的エクスポートサイト(Webflow、Framer:data-wf-site属性、website-files.com/framerusercontent.com CDN)は、コンポーネント再構築ではなく、生HTMLミラーとして最も高い忠実度でクローンします:プリハイドレーションサーバーHTMLをスケルトンとしてcurlでダウンロードし(キャプチャされたライブDOMはすでにハイドレーションされています—カルーセルクローンが挿入され、SplitText文字が分割され、スクリプトが再実行されると二重初期化されます)、すべてのCDN URLをセルフホストされたコピーに書き換え、アナリティクス/同意スクリプトを削除し、機能的なランタイム(サイトJS、GSAP、カルーセルライブラリ)はセルフホストのままにします。このルートの4つのルール:
- アセットは複数のCDNフォルダにまたがる場合があります(共有メディアライブラリ)。
cdn-host/<folder>/<file>は、サイトのメインフォルダプレフィックスではなく、ベース名で書き換えます。 - 書き換えられたすべてのリソースタグから
integrity/crossoriginを削除します。SRIハッシュは元のバイトに対して計算されており、ブラウザは変更されたファイルをサイレントに拒否します—症状はスタイルのないページ(ドキュメントの高さが爆発し、document.fontsが空)で、プローブから見えるエラーはありません。 - キーワードブロックリストによるトラッキングスクリプトの削除は、優雅なフォールバックを持つアナリティクスAPIに言及しているだけの機能的なスクリプトを殺してしまう可能性があります(ダウンロードボタンのOS検出テキスト置換を所有するヘッドスクリプトがPostHogを参照していました)。削除されたスクリプトリストを監査し、機能的なものをホワイトリストに登録します。
- 非決定論的な実験スクリプト(
Math.random()トラフィック分割、リダイレクトカナリア)は、機能的であっても削除されたままにします。ベンチマーク実行を再現不可能にします。
JSフレームワークサイト(Next.js、Remix—ハイドレーションミスマッチによりミラーが実現不可能)の場合、コンポーネント単位で再構築します:
- トップレベルのセクションごとに1つのコンポーネント。セクションのビジネスコンテンツにちなんで名前を付けます。
- フェーズ1からのトークンはそのまま
globals.cssに配置します。フォントは実際のウェイトマッピングでセルフホストします。 - 見えるものだけでなく、元のDOMマウント戦略をミラーリングします:オリジナルはすべてのカルーセルフレーム、ローテーションバリアント、アバターウォールをマウントしたまま(非表示)にしますが、条件付きレンダリングクローンは要素のごく一部で同じピクセルを表示し、ベンチマークでセクションごとのメディアカウントに失敗します。元のDOMのセクションごとの要素数を、そのスクリーンショットではなく照合します。
- ビジュアルだけでなく、元のコンテナ階層を複製します。
stickyはその親にスコープされます:フッターまで存続しなければならないナビは、ページ全体にまたがる親が必要です。カーテンレベルパネルは、効果が開始する場所から親が必要です。そうしないと、最初の画面からピン留めされます。セクションが再構築後に壊れた場合、最初にスティッキーまたはスタッキングコンテキストのスコープ変更を疑います。 - ビューポート固定要素(フローティングチャットバー)は、適切に構築されたオリジナルではbodyレベルの固定コンテナです:Reactポータルを使用し、セクション内で
absoluteを使用してはいけません。 - ドキュメントの高さを変更する折りたたみヘッダーは、Chromeのスクロールアンカリングと戦い、ジッターを引き起こします。堅牢なアーキテクチャ:フロー内に一定の高さのプレースホルダー(ドキュメントの高さは決して変化しない)を配置し、内部ヘッダーが完全にスクロールアウトされた後に
fixedに切り替え、スライドインアニメーションを追加します。高さをアニメーション化する必要がある場合は、overflow-anchor: noneと、折りたたみと展開のしきい値の間にヒステリシスを追加します。 - 装飾的なギザギザのエッジ(スカラップ/スタンプボーダー):ソリッドサイドのSVGをギャップに向けて反転させ、隣接要素に1pxのオーバーラップを持たせます。
フェーズ4、ベンチマークと収束
測定、ギャップ、ループ。このスキルは主張で動作するのではなく、数値で動作します。
ベンチマークプロトコルはreferences/eval.mdにあります。要約:
- 同じプローブ、同じランタイム、同じタブライフサイクル設定。 修正ループは、同じ
browser.openOrReuseTab+ ビデオフリーズ + ビューポートガード上で実行され、測定された差異は実際のレンダリングの違いのみです。 - 3つのスコア:
- ジオメトリスコア(重み0.5):それぞれのセクションについて、
(originalSection, cloneSection)のペアの y、h、w の平均絶対パーセント誤差(MAPE)。100%は同一。 - アセット/フォントスコア(重み0.3):クローンに存在するオリジナルのフォントファミリーとダウンロードされたメディアファイルの割合。
- アニメーションスコア(重み0.2):再生された
getAnimationsエントリ(アクティブなアニメーションと同じキーフレームパスと範囲を持つエントリ)の割合。 加重平均≥95%で合格。
- ジオメトリスコア(重み0.5):それぞれのセクションについて、
- 合格したら停止。 95%で終了し、主張された100%を追いかけてはいけません。残りの5%は、ベンチマークにカウントされない重要でないものです(ツールチップのフェード、無限ローダーのスピナー、アバターのランダム化)。
- 失敗した場合、ループ:両方のタブでスクリーンショットを取得し(ユーザーにスペースの1つで確認を依頼)、不一致を逸話的にではなく数値で説明し、どのコンポーネントを修正するかを決定し、優先順位を付け、そして再構築/測定します。ジオメトリスコアのみが上昇している場合でも、別のサイクルは価値があります。
サイクルをうまく進めるために:
- 差分は決して均一ではありません。 yがオフになる1つのセクションは、多くの場合、親のパディング/マージンが欠落しているか、または異なる親にスティッキーがスコープされていることを意味します。オフセットが複数のセクションにわたって一定の場合、それは
bodyスタイル、またはNext.jsの<main>/<Layout>のマージンが欠落していることを意味します。 - DOMカウントはスクリーンショットのピクセルを上書きします。 すべてのカルーセルスライドが同じサイズの場合、5つまたは8つのスライドはピクセル単位で同じように見えます。ベンチマークはDOMが嘘をつかないことを知っています。コンポーネントは、8の代わりに5つのスライドをレンダリングした場合、不合格になります。
フェーズ5、パッケージ化
ユーザーが明示的に求めない限り、依存関係管理やpackage.jsonの作成は行いません。ユーザーに、どの起動コマンドがサイトを提供するかを伝えます(例:npx serve public/、npm run dev)。
ユーザーが適切な共有を希望する場合(求められるまでは行わない):
# プロジェクトルートで実行
mkdir -p .clone && rm -rf .clone/assets && cp -r public .clone/assets && cp -r scripts .clone/ && cp references/eval.md .clone/BENCHMARK.md && cp references/install.md .clone/INSTALL.md && cp tools/*.py .clone/ 2>/dev/null; true
再現性のための最小限の指示を書きます(カバーノート、1パラグラフ)。
正確性ルール
- ビデオを最初に — すべての
page.evaluateの前に、page.evaluate(() => { document.querySelectorAll('video').forEach(v => v.pause()); setInterval(...) })。 - 空のビューポートをガード — プローブの前に
page.info()。w/hがゼロの場合、タブをリストアするよう要求します。 - width/height計算に
offset/clientプロパティを使用しない — それらはViewModel(サブピクセルレンダリングでは切り捨てられます)を読み取りますが、getBoundingClientRect()は実際のレンダリングピクセルを読み取ります。 innerTextではなくtextContentを常に使用する —innerTextは再フローをトリガーし、レイアウトスラッシングを引き起こし、遅いマシンでタイムアウトします。- クローンを比較する前に、その
document.titleを確認する — もっともらしいステータス200のcurl応答は、ブラウザがあなたのページをレンダリングしていることを証明しません。 eval_score.pyが真実を語る — 再構築は95%で終了します。逸話的な証拠(「見た目は正しい」)はスコアを上書きしません。
これらは、ネットワーク状態やローカルマシンの速度に関係なく、すべてのプローブが毎回同じ測定値を生成する必要があるため存在します。ランタイムの規則の違反は、デバッグ不可能なベンチマークエラーを引き起こします。
このスキルを使用するタイミング
使用するタイミング:ユーザーが次のように言った場合:「このウェブサイトをクローンして」「このランディングページをリバースエンジニアリングして」「このページをピクセルパーフェクトに再構築して」「このサイトのデザインをコピーして」「このサイトはどのようにそのスクロール効果を実現しているのか」「ウェブサイトを複製する」「ランディングページを複製する」「1:1で複製する」「サイトをリバースする」。
使用しないタイミング:ユーザーがスクリーンショットを必要としている場合(代わりにego-browserのscreenshotツールを使用)、記事のコンテンツをメモに取り込む場合(代わりに読みやすい抽出ツールを使用)、一般的なブラウザ自動化やデータスクレイピングの場合(代わりにego-browser/Jina/その他を使用)、ランディングページをライブで編集する場合、または「このCSSは何をするのか」のような少量のCSS/JSの質問の場合。
作業の洪水を防ぐために、曖昧な場合はこのスキルに優先的にルーティングしないでください。ユーザーが完全なクローンを求めていることが明確な場合にのみ使用します。
非クローン使用法(エージェントエンジニアリング)
このスキルは主にクローン作成用ですが、そのツールとプローブは、エージェントが自分のツールを構築するためのリファレンスとして役立ちます。
references/tool-design-template.md:測定可能なプローブを備えたエージェントツールのテンプレート。テスト不可能なツールはありません。tools/:実装されたツールの例。tools/manifest.json:このパターンに従うすべてのスキルがそのツールを登録するためのマニフェストスキーマ。- 方法論(プローブ、スナップショット、ディープコピー)は、視覚的出力を生成する任意のエージェントツールに一般化されます。