Skip to main content

Bundle do binário opencode

Este documento explica como o sidecar sst/opencode é empacotado dentro do Kognar Platform e o que o reviewer humano precisa validar antes de assinar/notarizar o DMG distribuído.

Task de origem: .ksdd/tasks/feature-opencode-coding-agent/002-bundle-opencode-binary.md Spike upstream: .planning/spikes/opencode-litellm-compat.md

1. Versão alvo e modelo de distribuição

ItemValor
Versão pinadav1.15.10
Asset usado (arm64)opencode-darwin-arm64.zip
Asset usado (x64)opencode-darwin-x64.zip
FormatoMach-O single-file (Bun-compiled standalone)
Tamanho zip (arm64)~35 MB
Tamanho descompactado (arm64)~102 MB
Assinatura upstreamadhoc — precisa re-sign na build Kognar
LicençaMIT — atribuir em "About" (task 017)

A versão é pinada em dois lugares que precisam ficar em sincronia:

  • scripts/fetch-opencode.mjs → constante OPENCODE_VERSION + map CHECKSUMS
  • electron/utils/opencodeBinary.ts → constante OPENCODE_VERSION

Ao bumpar a versão:

  1. Atualize OPENCODE_VERSION nos dois arquivos.
  2. Atualize os hashes SHA256 em CHECKSUMS (encontre-os em https://github.com/sst/opencode/releases/tag/vX.Y.Z).
  3. Rode npm run fetch:opencode -- --force localmente e teste smoke (opencode --version).

2. Como o bundle funciona

2.1 Em dev (npm run dev)

  1. npm installpostinstall chama scripts/fetch-opencode.mjs.
  2. Script detecta darwin-arm64, baixa o zip do GitHub Releases, verifica SHA256, descompacta para resources/opencode/bin/opencode, marca +x, escreve resources/opencode/.version para cache.
  3. electron/utils/opencodeBinary.ts → getOpencodeBinaryPath() resolve para <repoRoot>/resources/opencode/bin/opencode quando app.isPackaged === false.
  4. resources/opencode/ está em .gitignore — nunca é commitado.

Se você precisar forçar re-download:

npm run fetch:opencode -- --force

2.2 No build de produção (npm run package:mac:arm64)

  1. O script de package chama npm run fetch:opencode antes do electron-builder rodar, garantindo que resources/opencode/bin/opencode existe localmente.
  2. electron-builder copia o diretório para o .app via build.extraResources (configurado em package.json), com filtro bin/**/* — o .version fica fora do bundle.
  3. Layout final no .app: Kognar Platform.app/Contents/Resources/opencode/bin/opencode
  4. O hook scripts/afterPack-opencode.cjs roda automaticamente entre o copy e o code-sign do bundle externo:
    • Strip da assinatura ad-hoc do upstream.
    • Re-sign com a Developer ID identity resolvida pelo electron-builder.
    • Aplica --options runtime + as mesmas entitlements do app principal.
    • Verifica com codesign --verify --strict.
  5. electron/utils/opencodeBinary.ts → getOpencodeBinaryPath() resolve para process.resourcesPath + '/opencode/bin/opencode' quando app.isPackaged === true.

Se a Developer ID não estiver disponível (build local sem certs), o hook emite um warning e segue — o .app produzido funciona mas não passa notarization.

2.3 Entitlements

build/entitlements.mac.plist ganhou duas entradas necessárias para o Bun runtime embutido no binário:

EntitlementPor quê
cs.disable-library-validationBun carrega libs do sistema com Team ID diferente da Apple/da Kognar
cs.allow-dyld-environment-variablesLoader do Bun ajusta DYLD_* na boot

allow-jit e allow-unsigned-executable-memory já estavam presentes para o Electron — também são exigidos pelo Bun.

3. Como o reviewer humano valida o DMG

Estes passos exigem certs Apple Developer + API key de notarization, então não podem ser executados pelo agente automatizado.

# 1. Build full com sign + notarize (já configurado em scripts/deploy.mjs)
CSC_LINK=... CSC_KEY_PASSWORD=... \
APPLE_ID=... APPLE_APP_SPECIFIC_PASSWORD=... APPLE_TEAM_ID=... \
npm run package:mac:arm64

# 2. Confirmar que o binário está no DMG
hdiutil attach release/Kognar\ Platform-*.dmg
ls -lh "/Volumes/Kognar Platform/Kognar Platform.app/Contents/Resources/opencode/bin/opencode"
# → esperado: -rwxr-xr-x ... ~102M ... opencode

# 3. Verificar signature do binário interno
codesign -dvv "/Volumes/Kognar Platform/Kognar Platform.app/Contents/Resources/opencode/bin/opencode"
# → esperado: TeamIdentifier=<Kognar Team ID>, Signature=<DeveloperID>, NÃO 'adhoc'

# 4. Verificar bundle inteiro
codesign --verify --deep --strict --verbose=2 "/Volumes/Kognar Platform/Kognar Platform.app"
# → esperado: valid on disk + satisfies its Designated Requirement

# 5. spctl (Gatekeeper)
spctl -a -vv "/Volumes/Kognar Platform/Kognar Platform.app"
# → esperado: accepted, source=Notarized Developer ID

# 6. Cleanup
hdiutil detach "/Volumes/Kognar Platform"

Se qualquer passo falhar, recorra à seção 5 ("Troubleshooting").

4. Impacto no tamanho do DMG

Medição feita após o primeiro npm run fetch:opencode em darwin-arm64:

ComponenteTamanho
resources/opencode/bin/opencode (descompactado)~102 MB
Adicionado ao DMG (após compressão LZFSE do hdiutil)~40–50 MB esperado

O alerta da FEATURE §11 ("se passar de +50 MB, avaliar download lazy") fica no limite. Acompanhar o tamanho real do DMG após a primeira build de release e registrar em PR (se ultrapassar significativamente, abrir tarefa de v2 para download lazy on first run, conforme FEATURE §2.2).

5. Troubleshooting

Notarization falha com The binary is not signed with a valid Developer ID certificate

O hook afterPack-opencode.cjs não encontrou a identity. Confirme uma das:

  • CSC_LINK + CSC_KEY_PASSWORD no env (electron-builder convention).
  • CSC_NAME apontando para a identity exata no keychain.
  • Identity já importada no keychain de login e visível com security find-identity -v -p codesigning.

npm run fetch:opencode falha com checksum mismatch

GitHub provavelmente reempacotou o asset (raro). Compare o hash atual em https://github.com/sst/opencode/releases/tag/v1.15.10 e atualize CHECKSUMS em scripts/fetch-opencode.mjs. Não desative a verificação.

npm run fetch:opencode em CI Linux/Windows pula o download

Esperado — o feature é macOS-only em v1 (FEATURE §2.2). O script imprime warning e exits 0 para não quebrar npm install em runners não-mac.

Em runtime: isOpencodeAvailable() retorna false mesmo após install

Verifique:

ls -l resources/opencode/bin/opencode # dev
ls -l /Applications/Kognar\ Platform.app/Contents/Resources/opencode/bin/opencode # prod

Permissões devem incluir x para o owner. Em dev, rerode npm run fetch:opencode -- --force.