Communitygithub.com

xbmaxx/hermes-intro-cinematic

用於在 Hermes Desktop 中添加啟動動畫與自訂語音旁白。

hermes-intro-cinematic 是什麼?

此技能強制 Hermes Desktop 的開場動畫在每次啟動時播放,取代預設的單次播放限制。它讓開發者可以疊加自訂的語音旁白音軌,而不會影響原本的合成音效。此技能支援在原始碼中修改三個關鍵檔案(intro-reveal store、intro-root 元件、Electron 主程序),並提供完整的打包與重新簽署步驟,確保修改後的應用程式可正確運作。此外,還包含無需 Electron 環境的瀏覽器驗證方法,以及防止無限重播循環的「playedThisSession」標誌等注意事項。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add xbmaxx/hermes-intro-cinematic

在你喜歡的 AI 中提問

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

說明文件

Hermes 開場動畫

Hermes Desktop 已內建一段 22 秒的開場影片 — 包含七個節拍、即時立方體視埠、平行代理與 Nous 品牌標誌,並附有合成音效。該影片存在,但幾乎沒有人會看到第二次:播放條件為 guestOnboardingEnabled && !seen。

此技能讓影片在每次啟動時播放,並允許您在內建音效的基礎上疊加自訂語音旁白。

使用時機

  • 「讓 Hermes 啟動動畫每次都播放。」
  • 「將我的音訊放在開機動畫上 — 疊加,而非取代音效。」
  • 針對 apps/desktop/src/components/intro-reveal/ 的任何修改。

現有影片結構

項目路徑
閘門(播放時機)apps/desktop/src/components/intro-reveal/index.tsx
播放資格 + 階段儲存apps/desktop/src/store/intro-reveal.ts
覆蓋層啟動src/components/intro-reveal/intro-root.tsx
時鐘、計分、畫布use-intro-clock.ts、timeline.ts、viewport-cube.ts
合成音效提示sound.ts
原生視窗electron/intro-reveal-window.ts、electron/main.ts

時間計算:INTRO_TOTAL_MS = 17 600 是樂譜時間;實際時間為 × INTRO_PACE (1.25) = 22 000 毫秒,再加上 INTRO_EXIT_MS 900 毫秒 → 總共 23 125 毫秒。

每次啟動都重播 — 三個編輯,缺一不可

  1. src/store/intro-reveal.ts — isIntroRevealEnabled() 必須不再要求訪客引導;shouldPlayFirstRunIntro() 在非引導狀態下回傳 !playedThisSession。必須使用模組層級的 playedThisSession 標誌:閘門的 effect 會在階段回到 'hidden' 時重新執行,而影片結束時恰好會觸發此情況 — 沒有該標誌就會無限循環。
  2. src/components/intro-reveal/intro-root.tsx — 覆蓋層視窗會檢查 isOnboardingEnabled() 並在符合條件前返回,導致無法顯示。將其改為與主渲染器相同的判斷條件。
  3. electron/main.ts — introRevealController 建構時帶有 enabled: GUEST_ONBOARDING,因此主程序會拒絕 open。將其設為 true;活躍性由渲染器決定。

引導流程的 beginOnboardingFlow() / queueGuideAfterIntro() / takeGuideShape() 皆會自我檢查 isOnboardingEnabled(),因此在引導關閉時不會執行 — 無需額外修改。

關閉開關:localStorage['hermes-intro-reveal-always-v1'] = '0',或使用 HERMES_SKIP_INTRO=1 / --skip-intro 啟動。

疊加音訊軌道

將檔案置於 apps/desktop/public/intro-voiceover.mp3(Vite 會將 public/ 原樣複製到 dist/),然後在 sound.ts 中:

export function startVoiceover(cutAtMs: number): { stop: () => void }

— 建立一個 Audio 元素,設定 volume,開始播放,並使用 setTimeout(stop, cutAtMs - 300) 使最後 300 毫秒淡出而非突然中斷。在 use-intro-clock.ts 中與 startPad() 相鄰處呼叫它,並在 effect 清理時停止。

請使用 <audio> 元素,切勿使用 fetch() + decodeAudioData()。 打包後的渲染器在 file:// 上執行,fetch 和 decodeAudioData 會因 CORS 而被阻擋;媒體元素則可正常載入 file:// 資產。

兩個音軌本質上互相獨立 — 元素音訊與 WebAudio 提示在作業系統層級混合,無需任何衰減處理。將 $hapticsMuted 連接至兩者,並訂閱而非僅讀取一次,否則靜音切換會不同步。

裁剪點:

const cutMs = Math.round((INTRO_TOTAL_MS + INTRO_EXIT_MS) * INTRO_PACE)  // 23 125

發布至打包應用程式

僅修改原始碼不會生效,必須重新打包 asar。這裡有兩個值得檢查的陷阱:

找出使用者實際啟動的應用程式 — 切勿假設為 /Applications。

ps aux | grep '[H]ermes.app/Contents/MacOS/Hermes'
defaults read com.apple.dock persistent-apps | grep -o '/[^"']*Hermes\.app'

Hermes 更新會放置在 ~/.hermes/hermes-agent*/apps/desktop/release/mac-arm64/Hermes.app,而 Dock 通常指向該處;反之 /Applications/Hermes.app 可能是過時的 0.0.0 副本。請以 CFBundleShortVersionString 確認(0.17.x 為正式版本,0.0.0 則不是)。

也請重新整理 app.asar.unpacked/dist/。 打包構建會將 dist/** 列在 asarUnpack 中,而 resolveRendererIndex() 會優先使用該複本 — 僅重新打包 asar 會導致舊的 unpacked 樹狀結構遮蔽變更。

npm install --workspace apps/desktop --ignore-scripts \
  --engine-strict=false --min-release-age=0
cd apps/desktop && node ../../node_modules/vite/bin/vite.js build
node scripts/bundle-electron-main.mjs     # 主程序修改也需要此步驟

asar extract "$RES/app.asar" "$WORK"
rm -rf "$WORK/dist" && cp -R apps/desktop/dist "$WORK/dist"
asar pack "$WORK" "$WORK/app.asar"
mv -f "$WORK/app.asar" "$RES/app.asar"
cp -R apps/desktop/dist/. "$RES/app.asar.unpacked/dist/"
codesign --force --deep -s - "$APP"      # 臨時簽章;Hermes 使用臨時簽章發布

在網路緩慢時,--ignore-scripts 很重要:下載 Electron 二進位檔並非建構或重新打包所需,僅在執行開發版本時才需要。

在不使用 Electron 的情況下驗證

在普通瀏覽器中渲染影片 — 無需打包,無需下載二進位檔:

cd apps/desktop && node ../../node_modules/vite/bin/vite.js --host 127.0.0.1 --port 5174
page.add_init_script("""
window.hermesDesktop = {
  guestOnboardingEnabled: true, skipIntro: false,
  introReveal: { ready(){}, skip(){},
    open(){ return Promise.resolve() }, close(){ return Promise.resolve() },
    onSkip(){ return () => {} }, onClosed(){ return () => {} } }
};
""")
page.goto("http://127.0.0.1:5174/?win=intro")
page.evaluate("import('/src/components/intro-reveal/intro-root.tsx').then(m => m.mountIntroReveal())")

路由自身的掛載呼叫會在沒有橋接時不執行;在模擬後手動匯入才是實際繪製的關鍵。若要檢查音訊軌道,請模擬 window.Audio 並記錄實例 — 確認 currentTime 隨著影片前進,且 paused === true, volume === 0 在裁剪點處成立。

值得保留在 src/store/intro-reveal.test.ts 中的合約測試:

  • 在正常啟動時播放,即使已設定 hermes-intro-reveal-seen-v1
  • 僅播放一次 — finishIntroReveal() 必須不能重新啟用
  • 尊重 opt-out 鍵與 skipIntro 啟動標誌
  • 不影響引導流程的首次執行語意

陷阱

  • 移除 playedThisSession 會導致無限重播循環,而非無錯誤的啟動。
  • INTRO_TOTAL_MS 是樂譜時間。忘記 INTRO_PACE 會使語音旁白提早 4.4 秒結束。
  • 主程序的 enabled 標誌與覆蓋層自身的保護是獨立的閘門;遺漏任何一個都會導致沒有任何畫面渲染,且不顯示錯誤。
  • 四個獨立層級確保畫面能重新顯示(正常關閉、Esc + 1.2 秒備援、本機 deadman、34 秒主程序看門狗)。在調整時間時請勿移除其中任何一個。
  • hermes update 會將應用程式更換為官方版本。修改會保留在 Git 中;更新後請重新執行安裝程式。

相關技能