Hermes Intro Cinematic
Hermes Desktop enthält bereits einen 22-sekündigen Intro-Film – sieben Beats, ein Live-Cube-Viewport, parallele Agenten, die Nous-Marke – mit synthetisiertem Sound. Er existiert, aber fast niemand sieht ihn zweimal: Die Berechtigung ist guestOnboardingEnabled && !seen.
Diese Fähigkeit sorgt dafür, dass der Film bei jedem Start abgespielt wird, und ermöglicht es, eine eigene Sprachspur unter die eingebauten Cues zu legen.
Wann verwenden
- „Lass die Hermes-Startanimation jedes Mal abspielen.“
- „Lege meine Audiospur auf die Boot-Animation – darüber, nicht als Ersatz für die Soundeffekte.“
- Jede Änderung an
apps/desktop/src/components/intro-reveal/.
Was der Film bereits ist
| Teil | Pfad |
|---|---|
| Tor (wann er abgespielt wird) | apps/desktop/src/components/intro-reveal/index.tsx |
| Berechtigung + Phasenspeicher | apps/desktop/src/store/intro-reveal.ts |
| Overlay-Boot | src/components/intro-reveal/intro-root.tsx |
| Takt, Punktzahl, Canvas | use-intro-clock.ts, timeline.ts, viewport-cube.ts |
| Synthesizer-Cues | sound.ts |
| Natürliches Fenster | electron/intro-reveal-window.ts, electron/main.ts |
Timing-Mathematik: INTRO_TOTAL_MS = 17 600 ist Partitur-Zeit; Wandzeit ist
× INTRO_PACE (1.25) = 22 000 ms, plus INTRO_EXIT_MS 900 ms → 23 125 ms insgesamt.
Wiederholung bei jedem Start – drei Änderungen, alle erforderlich
src/store/intro-reveal.ts–isIntroRevealEnabled()darf nicht mehr die Gast-Einführung voraussetzen;shouldPlayFirstRunIntro()gibt!playedThisSessionaußerhalb der Einführung zurück. Eine modulebeneplayedThisSession-Flagge ist zwingend erforderlich: Der Effekt des Tors wird immer dann erneut ausgeführt, wenn die Phase zu'hidden'zurückkehrt – genau das passiert, wenn der Film endet – ohne die Flagge würde er sich unendlich wiederholen.src/components/intro-reveal/intro-root.tsx– das Overlay-Fenster prüftisOnboardingEnabled()und würde vor dem Zeichnen zurückkehren. Ändere es auf dasselbe Prädikat wie den Haupt-Renderer.electron/main.ts–introRevealControllerwird mitenabled: GUEST_ONBOARDINGkonstruiert, sodass der Hauptprozessopenablehnt. Setze es auftrue; die Lebendigkeit wird im Renderer entschieden.
beginOnboardingFlow() / queueGuideAfterIntro() / takeGuideShape() schützen sich alle selbst mit isOnboardingEnabled(), sodass sie bei deaktivierter Einführung nichts tun – keine zusätzliche Änderung nötig.
Aus-Schalter: localStorage['hermes-intro-reveal-always-v1'] = '0', oder starte mit HERMES_SKIP_INTRO=1 / --skip-intro.
Eine Audiospur überlagern
Lege eine Datei unter apps/desktop/public/intro-voiceover.mp3 ab (Vite kopiert public/ unverändert nach dist/), dann in sound.ts:
export function startVoiceover(cutAtMs: number): { stop: () => void }
– erstelle ein Audio-Element, setze volume, starte es und setTimeout(stop, cutAtMs - 300), damit die letzten 300 ms ausklingen statt zu klicken. Rufe es neben startPad() in use-intro-clock.ts auf und stoppe es im Effekt-Cleanup.
Verwende ein <audio>-Element, niemals fetch() + decodeAudioData(). Der gepackte Renderer läuft auf file://, wo fetch und decodeAudioData durch CORS blockiert werden; Media-Elemente laden file://-Assets problemlos.
Die beiden Spuren sind von Natur aus unabhängig – Element-Audio und WebAudio-Cues mischen sich auf Betriebssystemebene. Nichts dämpft etwas. Verbinde $hapticsMuted mit beiden und abonniere es, anstatt es einmal zu lesen, sonst gerät der Stummschaltungs-Toggle außer Sync.
Schnittpunkt:
const cutMs = Math.round((INTRO_TOTAL_MS + INTRO_EXIT_MS) * INTRO_PACE) // 23 125
In eine gepackte App ausliefern
Quellcode-Änderungen wirken erst, wenn das ASAR neu gepackt wird. Hier zwei Fallstricke, die sich lohnen zu prüfen:
Finde die App, die der Benutzer tatsächlich startet – gehe niemals von /Applications aus.
ps aux | grep '[H]ermes.app/Contents/MacOS/Hermes'
defaults read com.apple.dock persistent-apps | grep -o '/[^"]*Hermes\.app'
Hermes-Updates landen in ~/.hermes/hermes-agent*/apps/desktop/release/mac-arm64/Hermes.app und das Dock zeigt oft dorthin, während /Applications/Hermes.app eine veraltete 0.0.0-Kopie sein kann. Bestätige mit CFBundleShortVersionString (0.17.x ist echt, 0.0.0 nicht).
Aktualisiere auch app.asar.unpacked/dist/. Gepackte Builds listen dist/** in asarUnpack auf, und resolveRendererIndex() bevorzugt diese Kopie – wenn nur das ASAR neu gepackt wird, überschattet der veraltete entpackte Baum es.
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 # Hauptprozess-Änderungen benötigen diesen Schritt ebenfalls
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" # ad-hoc; Hermes wird ad-hoc signiert ausgeliefert
--ignore-scripts ist bei langsamen Netzwerken wichtig: Der Electron-Binär-Download wird zum Erstellen oder Neupacken nicht benötigt, sondern nur zum Ausführen des Dev-Modus.
Ohne Electron verifizieren
Rendere den Film in einem einfachen Browser – kein Packen, kein Binär-Download:
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())")
Der eigene Mount-Aufruf der Route tut ohne die Brücke nichts; der manuelle Import nach dem Stubbing ist das, was tatsächlich zeichnet. Um die Audiospur zu prüfen, stubbe window.Audio und zeichne Instanzen auf – bestätige, dass currentTime mit dem Film fortschreitet und dass paused === true, volume === 0 am Schnittpunkt.
Vertragstests, die es wert sind, in src/store/intro-reveal.test.ts behalten zu werden:
- Der Film wird bei einem normalen Start abgespielt, selbst wenn
hermes-intro-reveal-seen-v1gesetzt ist - Der Film wird genau einmal abgespielt –
finishIntroReveal()darf ihn nicht erneut scharfschalten - Der Opt-out-Schlüssel und das
skipIntro-Startflag werden beachtet - Die Einführungslogik für den ersten Durchlauf bleibt unberührt
Fallstricke
- Das Entfernen von
playedThisSessionführt zu einer unendlichen Wiederholungsschleife, nicht zu einem fehlerfreien Start. INTRO_TOTAL_MSist Partitur-Zeit. Das Vergessen vonINTRO_PACEschneidet die Sprachspur 4,4 s zu früh ab.- Das
enabled-Flag des Hauptprozesses und die eigene Wächterfunktion des Overlays sind separate Tore; wird eines verpasst, wird nichts gerendert und es erscheint kein Fehler. - Vier unabhängige Schichten halten den Bildschirm zurück (normales Schließen, Esc + 1,2 s Fallback, lokaler Deadman, 34-s-Hauptprozess-Watchdog). Entferne keine, während du das Timing änderst.
hermes updateersetzt die App durch den offiziellen Build. Der Patch bleibt in Git bestehen; führe den Installer nach dem Update erneut aus.