Communitygithub.com

xbmaxx/hermes-intro-cinematic

Use quando quiser adicionar uma animação de inicialização cinematográfica ao Hermes Desktop.

O que é hermes-intro-cinematic?

Esta habilidade permite reproduzir a animação de inicialização cinematográfica do Hermes Desktop em toda execução, ao invés de apenas na primeira inicialização com onboarding. O Hermes já inclui um filme de 22 segundos com sete beats, um cubo 3D, agentes paralelos e a marca Nous, mas ele só é exibido uma vez para novos usuários. Com esta skill, a animação toca sempre, e você pode sobrepor sua própria faixa de narração mantendo os efeitos sonoros originais. Ela suporta plataformas como Hermes Desktop (Electron) e é útil para desenvolvedores que personalizam a experiência de boot. O ajuste requer três edições no código fonte (store, overlay e processo principal), além de instruções detalhadas para empacotamento e verificação sem Electron.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add xbmaxx/hermes-intro-cinematic

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Cinemática de Introdução do Hermes

O Hermes Desktop já vem com um filme de introdução de 22 segundos — sete beats, um cubo 3D, agentes paralelos, a marca Nous — com som sintetizado. Ele existe, mas quase ninguém o vê duas vezes: a elegibilidade é guestOnboardingEnabled && !seen.

Esta skill faz o filme tocar em toda inicialização e permite que você adicione sua própria faixa de narração sobre os sinais embutidos.

Quando usar

  • "Faça a animação inicial do Hermes tocar sempre."
  • "Coloque meu áudio na animação de boot — em camadas, não substituindo os efeitos sonoros."
  • Qualquer alteração em apps/desktop/src/components/intro-reveal/.

O que o filme já contém

PeçaCaminho
Portão (quando toca)apps/desktop/src/components/intro-reveal/index.tsx
Elegibilidade + store de faseapps/desktop/src/store/intro-reveal.ts
Boot de sobreposiçãosrc/components/intro-reveal/intro-root.tsx
Clock, pontuação, canvasuse-intro-clock.ts, timeline.ts, viewport-cube.ts
Sinais sonorossound.ts
Janela nativaelectron/intro-reveal-window.ts, electron/main.ts

Matemática de tempo: INTRO_TOTAL_MS = 17 600 é o tempo da partitura; o tempo real é × INTRO_PACE (1.25) = 22 000 ms, mais INTRO_EXIT_MS 900 ms → 23 125 ms no total.

Reproduzir em toda inicialização — três edições, todas necessárias

  1. src/store/intro-reveal.ts — isIntroRevealEnabled() deve parar de exigir onboarding do convidado; shouldPlayFirstRunIntro() retorna !playedThisSession fora do onboarding. Uma flag playedThisSession no nível do módulo é obrigatória: o efeito do portão é reexecutado sempre que a fase retorna para 'hidden', que é exatamente o que terminar o filme faz — sem a flag, ele entra em loop infinito.
  2. src/components/intro-reveal/intro-root.tsx — a janela de sobreposição protege com isOnboardingEnabled() e retornaria antes de pintar. Troque para o mesmo predicado do renderizador principal.
  3. electron/main.ts — introRevealController é construído com enabled: GUEST_ONBOARDING, então o processo principal rejeita open. Defina como true; a vivacidade é decidida no renderizador.

O beginOnboardingFlow() / queueGuideAfterIntro() / takeGuideShape() do onboarding se autoprotegem com isOnboardingEnabled(), então eles não operam quando o onboarding está desligado — nenhuma alteração extra é necessária.

Interruptor: localStorage['hermes-intro-reveal-always-v1'] = '0', ou inicie com HERMES_SKIP_INTRO=1 / --skip-intro.

Adicionar uma faixa de áudio em camadas

Coloque um arquivo em apps/desktop/public/intro-voiceover.mp3 (o vite copia public/ para dist/ literalmente), então em sound.ts:

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

— crie um elemento Audio, defina volume, inicie e setTimeout(stop, cutAtMs - 300) para que os últimos 300 ms diminuam em vez de estalar. Chame-o ao lado de startPad() em use-intro-clock.ts e pare-o na limpeza do efeito.

Use um elemento <audio>, nunca fetch() + decodeAudioData(). O renderizador empacotado roda em file://, onde fetch e decodeAudioData são bloqueados por CORS; elementos de mídia carregam assets file:// sem problemas.

As duas faixas são independentes por construção — o áudio do elemento e os sinais WebAudio se misturam no nível do sistema operacional. Nada é silenciado. Conecte $hapticsMuted a ambas e assine em vez de ler uma vez, ou o alternador de mudo dessincroniza.

Ponto de corte:

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

Enviar para um app empacotado

As edições no código fonte não fazem nada até que o asar seja reempacotado. Duas armadilhas aqui, ambas valem a verificação:

Encontre o app que o usuário realmente inicia — nunca presuma /Applications.

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

As atualizações do Hermes vão para ~/.hermes/hermes-agent*/apps/desktop/release/mac-arm64/Hermes.app e o Dock muitas vezes aponta para lá, enquanto /Applications/Hermes.app pode ser uma cópia antiga 0.0.0. Confirme com CFBundleShortVersionString (0.17.x é real, 0.0.0 não é).

Atualize também app.asar.unpacked/dist/. As builds empacotadas listam dist/** em asarUnpack, e resolveRendererIndex() prefere essa cópia — reempacotar apenas o asar e manter a árvore desempacotada antiga a obscurece.

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     # edições no processo principal precisam deste passo também

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; o Hermes vem assinado ad-hoc

--ignore-scripts é importante em redes lentas: o download do binário Electron não é necessário para construir ou reempacotar, apenas para executar o dev.

Verificar sem Electron

Renderize o filme em um navegador comum — sem empacotamento, sem download de binário:

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())")

A chamada de montagem da própria rota não opera sem a ponte; a importação manual após o stub é o que realmente pinta. Para verificar a faixa de áudio, crie um stub de window.Audio e registre as instâncias — afirme que currentTime avança com o filme e que paused === true, volume === 0 no corte.

Testes de contrato que valem manter em src/store/intro-reveal.test.ts:

  • toca em uma inicialização normal mesmo quando hermes-intro-reveal-seen-v1 está definido
  • toca exatamente uma vez — finishIntroReveal() não deve rearmá-lo
  • respeita a chave de desativação e a flag skipIntro de inicialização
  • deixa a semântica de primeira execução do onboarding intacta

Armadilhas

  • Remover playedThisSession causa um loop infinito de reprodução, não uma inicialização sem bugs.
  • INTRO_TOTAL_MS é tempo da partitura. Esquecer INTRO_PACE corta a narração 4,4 s antes.
  • A flag enabled do processo principal e a própria proteção da sobreposição são portões separados; perca qualquer um e nada renderiza, sem erro.
  • Quatro camadas independentes mantêm a tela voltando (fechamento normal, Esc + 1,2 s de fallback, deadman local, watchdog do processo principal de 34 s). Não remova uma enquanto altera o tempo.
  • hermes update substitui o app pela build oficial. O patch persiste no git; execute novamente o instalador após a atualização.

Habilidades Relacionadas