Communitygithub.com

echarrod/skillsed

將檔案的 Git 歷史記錄轉為動畫,每個視覺上不同的版本作為一幀——適合展示標誌的演變、頁面的重新設計。當被要求為標誌、圖片、圖示或網頁製作動畫、重播或顯示其演變過程時使用。

skillsed 是什麼?

animate-file-history 是一款 AI 代理技能,專門將儲存庫中某個檔案(特別是圖片或網頁)的 Git 歷史記錄,轉換為 GIF 或 MP4 動畫。它會自動識別使用者自然語言請求中的目標檔案,遍歷 Git 歷史中的每個版本,渲染視覺內容,去除視覺上相同的版本,然後將剩餘的幀合併為流暢的動畫。該技能支援 PNG、JPG、SVG 等多種圖片格式,以及靜態 HTML 和基於框架(如 React、Next.js、Vue、Astro)的頁面。對於框架頁面,它會自動擷取每個提交的螢幕截圖,確保準確再現歷史外觀。此技能適用於 Claude Code、Cursor 等基於命令列的 AI 編碼代理。其核心架構是自動化處理腳本(`animate-history.sh`),開發者只需提供檔案路徑或描述,AI 就能自動完成從 Git 歷史探索到最終動畫產出的整個流程。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/echarrod/skillsed/tree/HEAD/skills/animate-file-history

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

animate-file-history

為某個東西在儲存庫生命週期內的每個樣貌建立 GIF(或 MP4)動畫。請求是以自然語言給出的($ARGUMENTS),因此你的工作是:找出它指的是哪些檔案,從 Git 收集每個版本,渲染每個版本,刪除與前一版看起來相同的版本,然後將剩下的串成動畫。

內建的腳本處理 Git 遍歷、渲染、視覺去重和編碼: scripts/animate-history.sh,相對於此技能的基礎目錄——而不是使用者的儲存庫。 以下,$SKILL 代表該目錄;請使用其絕對路徑來呼叫腳本。--help 列出所有選項。你的工作是選擇檔案,對於頁面,則是產生螢幕截圖。

1. 檢查工具

command -v git ffmpeg rsvg-convert; ls "/Applications/Google Chrome.app" 2>/dev/null; command -v chromium google-chrome

ffmpeg 是必需的。SVG 需要 rsvg-convert(brew install librsvg / apt install librsvg2-bin)或 Chrome/Chromium。--label 說明文字和 HTML 頁面需要 Chrome/Chromium(CHROME=/path 可覆蓋自動偵測)。如果缺少某個工具,請說明缺乏什麼以及如何安裝——不要默默地略過 SVG 版本。

2. 將請求解析為檔案

搜尋目前分支的整個歷史記錄,而不僅僅是今天的檔案樹——標誌會被重新命名、移動和重新匯出,而刪除的檔案仍然屬於故事的一部分:

git log --format= --name-only -- ':(glob,icase)**/*logo*' | sort -u

搜尋那個東西本身的名稱,而不僅僅是使用者的用詞。一個標誌也可能存在於 favicon*、apple-touch-icon*、*mark*、*lockup*、*wordmark*、*brand* 和產品本身的名稱中——早期版本通常只存在於 favicon 中。請據此擴大 glob 列表,並查看回傳的結果。

過濾為可渲染的類型:圖片使用 png jpg jpeg gif webp bmp svg;靜態頁面使用 html。忽略建置輸出和複製的檔案(dist/、build/、node_modules/、.next/)。

然後將匹配結果分組為譜系——每個動畫一個譜系:

  • 同一個作品在重新命名、移動或格式變更後(例如 logo.png → brand/logo.svg)屬於同一個譜系。將所有路徑傳遞給一次執行;腳本會按提交日期將它們合併到單一時間軸。
  • 同時存在的變體(logo-dark.svg、logo-mono.svg、favicon.png、不同尺寸的匯出,如 [email protected])屬於不同的譜系。預設情況下,每個譜系產生一個動畫,並且只製作請求合理涵蓋的變體的動畫(通常是主要標誌,而不是每個 favicon 尺寸)。

如果分組確實不明確(幾個不相關的「logo」檔案,或沒有任何匹配),請顯示候選清單及其提交數量(git log --follow --oneline -- <path> | wc -l),然後在渲染前詢問。

3a. 圖片:執行腳本

"$SKILL/scripts/animate-history.sh" -o logo-history.gif --label <路徑> [<較舊路徑> ...]

有用的選項:使用 -s 512 或 -s 1200x800 設定畫布大小,-t 0.25 設定每個版本的秒數(圖片預設;透過 --frames 進行的頁面螢幕截圖預設為 0.8;如果使用者提供了數值,則使用使用者的值,例如「每幀 0.5 秒」→ -t 0.5),--hold 1.5 設定最後一幀的停留秒數,--bg '#0b0b0b' 用於為深色背景設計的標誌,--keep DIR 保留幀,-o x.mp4 輸出影片,--skip <sha>(可重複)跳過使用者不想要的版本。它會在 stderr 輸出每個提交的 keep/skip 以及 kept N of M versions(保留了 M 個版本中的 N 個)。

路徑相對於目前目錄(或使用 -C DIR),就像任何 Git pathspec 一樣;從儲存庫根目錄執行,並像上面的歷史列表所顯示的那樣傳遞路徑。腳本本身會處理重新命名(git log --follow),所以除非檔案以新名稱重新建立,否則目前的路徑通常就足夠了。

3b. 頁面:對每個版本進行螢幕截圖,然後對該資料夾執行腳本

頁面需要在每個提交時進行渲染。找到頁面的原始檔(index.html、app/page.tsx、src/pages/index.vue,……)並列出修改過該檔案或其渲染內容的提交——首頁的外觀也會透過其元件和 CSS 改變:

git log --format='%H %cs' --reverse -- <頁面檔案> <其元件/樣式目錄>

如果超過約 40 個提交,則進行取樣(每週或每月一次,加上第一個和最後一個),並說明此情況。螢幕截圖放在一個名為 <YYYY-MM-DD>_<short-sha>.png 的資料夾中,這樣檔案會按日期排序,而名稱就成為說明文字。

  • 靜態 HTML:無需建置。使用 git archive <sha> | tar -x -C "$snap" 匯出提交,然後使用以下命令對檔案進行螢幕截圖: "$CHROME" --headless=new --hide-scrollbars --window-size=1280,800 --screenshot=... "file://$snap/<page>.html"。 之後刪除 $snap,並將其保留在儲存庫之外。

  • 框架頁面(React、Next、Vue、Astro,……):它必須執行才能渲染。對每個提交使用 snapshot-commit.sh:

    "$SKILL/scripts/snapshot-commit.sh" -C <儲存庫> -r /<路由> -f https://<正式網站> <sha> "$shots"
    

    它會匯出提交,安裝該提交自己的鎖定檔案(每個鎖定檔案快取),啟動開發伺服器(如果開發命令不是 npm run dev -- --port {port},則使用 -d 'cmd with {port}'),並透過 Playwright(scripts/screenshot-page.mjs,需要 playwright-core 和 Chrome——儲存庫自己的 Chrome 是透過 PLAYWRIGHT_DIR 找到的)進行螢幕截圖。它會等待網路空閒和每個 <img> 載入完成,關閉 cookie 橫幅,然後輸出一行:ok、blank、error(錯誤網頁,例如「無法在此瀏覽器中載入」)或 fail(安裝或開發伺服器失敗)。只有 ok 會寫入 PNG。如果這意味著要進行多次安裝,請在執行前詢問。

在製作動畫前檢查結果,不要只相信 ok:

  • 永遠不要借用當前的 node_modules。 舊的提交會匯入目前樹中已丟棄的套件(例如 react-router-dom),導致頁面渲染為空白。這就是腳本會為每個提交分別安裝的原因。
  • blank / error / fail 提交:查看開發日誌,嘗試修復一次原因(缺少環境變數、錯誤的 -d 命令、--ignore-scripts 跳過了必要的建置步驟),否則跳過該提交並說明跳過了多少個。
  • 損壞的圖片(ok 行上的 broken-images=N,以及 URL):開發伺服器無法提供的資源(平台託管的路徑如 /__l5e/...、被 gitignore 忽略或上傳的檔案)。-f <正式網站> 會從正式環境取得它們。如果圖片仍然損壞,請在報告中說明——不要默默地產出一個充滿空白方塊的 GIF。主動修復,而不是事後報告。
  • 在最終執行前,先製作一個幀的縮圖索引(ffmpeg -pattern_type glob -i '*.png' -vf scale=320:-1,tile=6x6 -frames:v 1 sheet.png)。

然後:

"$SKILL/scripts/animate-history.sh" --frames "$shots" -s 1280x800 --label -o home-page-history.gif

相同的螢幕截圖會以與相同圖片相同的方式被刪除,空白(單一純色)的也會被刪除(使用 --keep-blank 保留它們)。對每個拍攝使用固定的視窗大小,對於較長或詳細的頁面,建議使用 -o x.mp4——全頁 GIF 會變得很大。

3c. 跨多個儲存庫

當標誌存在於多個儲存庫中時(一個應用程式、一個儀表板、一個網站),在每個儲存庫中使用 --keep <dir> 並不帶 --label 執行腳本,然後將每個保留的幀複製到一個名為 <YYYY-MM-DD>_<repo>-<short-sha>.png 的資料夾中——stderr 上的 keep 行會按順序給出每個保留幀的日期和 sha。對衝突的名稱添加 -2、-3(一次提交中多個檔案變更)。然後在合併後的資料夾上執行 --frames <folder> --label -t 0.25,為所有儲存庫建立一個統一的時間軸。

4. 報告

提供輸出路徑、找到的版本數量以及成功進入動畫的數量、日期範圍,以及任何被跳過的內容(無法渲染、建置失敗、被取樣掉)。如果被要求,則打開它(open x.gif)。將輸出寫入使用者想要的任何位置,預設為目前目錄——除非被要求,否則永遠不要提交它。

注意事項

  • 去重是基於渲染後的像素,而非位元組。 如果重新匯出的 PNG 或重新格式化的 SVG 渲染結果相同,則會被跳過;一個像素的變動則算作一個變更。這就是重點——不要在其上添加位元組層級的過濾器。
  • 引用外部檔案的 SVG(字型、<image href>)在沒有這些外部檔案的情況下渲染,因為只有 SVG 本身是從歷史記錄中提取的。內嵌內容則沒問題。
  • 動畫 GIF 來源只會貢獻其第一幀。
  • 合併提交不會被遍歷——git log 顯示的是檔案在產生變更的分支上的變更,這正是時間軸所需要的。
  • 淺層複製(CI、--depth 1)沒有歷史記錄。請檢查 git rev-parse --is-shallow-repository,如果輸出 true,則建議執行 git fetch --unshallow。
  • 說明文字需要 Chrome/Chromium,因為捆綁的 ffmpeg 建置通常缺少 drawtext。

模型與努力程度

這裡的判斷工作量很小(選擇檔案、分組譜系、選擇取樣);大部分繁重工作由腳本和 shell 迴圈完成。預設使用 Sonnet 搭配低努力程度。Opus 或更高努力程度對這個任務沒有好處,也不需要 fork 或子代理。只有在譜系分組確實不明確時才提高努力程度。

大多數執行的 token 消耗來自會話的繼承上下文(CLAUDE.md、技能和工具列表,約 10 萬+ token),每次輪次都會從快取中重新讀取,而不是來自工作本身。在一個精簡的會話中執行,成本遠低於在一個負載較重的會話中執行。

基準測試

使用會話記錄來測量(匯總助手輪次中的 usage):

f=$(ls -t ~/.claude/projects/<project-dir>/*.jsonl | head -1)
jq -s '[.[]|select(.message.usage)|.message.usage]|{turns:length,out:(map(.output_tokens)|add),cache_read:(map(.cache_read_input_tokens)|add),cache_create:(map(.cache_creation_input_tokens)|add)}' "$f"
日期模型 / 努力程度任務輪次輸出 token快取讀取快取建立結果
2026-10-23Sonnet 5.5 / lowstorra 首頁,僅探索(渲染前)93.6k731k134k在約 7 次呼叫中找到檔案和路由歷史

每次完整執行(不僅是探索)添加一行;僅在 Sonnet-low 執行的譜系或取樣錯誤時,才比較 Opus / 更高努力程度。

相關技能