Release builds¶
This page mirrors implementation notes maintained in CLAUDE.md.
Update both when changing this subsystem.
Cue ships as a signed, notarized .app / DMG (macOS) and a
PyInstaller-built .exe + Inno Setup installer (Windows). The
macOS path is the more involved of the two due to hardened runtime
+ Apple notary requirements.
macOS — Nuitka + Developer ID + notarization¶
Single command:
bash scripts/build_nuitka.sh
What that script does:
- Activates the conda
cueenv (uv-built env is for dev; the build env is conda for ABI consistency between Python, PyGObject, GStreamer, and the GStreamer plugins). Seescripts/environment.yml. - Compiles via Nuitka into a
.appbundle. - Bundles dist-info from the vendored owa packages so
importlib.metadata.entry_points()discovers the plugin registrations. - Resolves GStreamer.framework from the system path (not bundled
— too large; users install GStreamer system-wide). A small
libiconvcompatibility shim is built so PyGObject finds the GNUiconv*symbol set. - Bundles the
llama-serverbinary underCue.app/Contents/MacOS/bin/llama-b<tag>/for on-device inference (downloaded automatically by the script if missing). - Codesigns every Mach-O binary serially with the Developer
ID Application certificate, hardened runtime, embedded
entitlements (
scripts/release/entitlements.plist), and a secure timestamp. Retries on transient timestamp-server hiccups. - Notarizes the
.appviaxcrun notarytool submit --waitusing a keychain profile (configured viaxcrun notarytool store-credentials). - Staples the notary ticket onto the
.app. - Builds a DMG, signs it with the same Developer ID identity, submits to the notary, staples.
Artifacts land in dist/Cue.app and dist/Cue-<version>.dmg.
Hardened runtime entitlements¶
scripts/release/entitlements.plist:
com.apple.security.cs.disable-library-validation— required because GStreamer.framework dylibs are not signed by our team-id.com.apple.security.cs.allow-dyld-environment-variables—run.pyre-execs itself after settingDYLD_LIBRARY_PATH/GST_PLUGIN_PATH/GI_TYPELIB_PATHto point at the system GStreamer.framework. Hardened runtime strips these env vars unless this entitlement is granted.com.apple.security.cs.allow-jit— required because GStreamer'sorcSIMD JIT compiler callspthread_jit_write_protect_npto toggle W^X memory.
Apple Developer prerequisites¶
- Apple Developer Program membership.
- Developer ID Application certificate installed in the login
keychain (verify with
security find-identity -v -p codesigning). xcrun notarytool store-credentials cue-notarizerun once with Apple ID + app-specific password + Team ID.security set-key-partition-list -S apple-tool:,apple:run once on the cert so codesign doesn't prompt for keychain access on every binary.
The build script auto-detects the Developer ID identity via
security find-identity and falls back to ad-hoc signing
(--sign -) when none is present — useful for local-only smoke
testing without distribution.
Selftests¶
The frozen build supports selftests for catching missing native libraries before users hit them:
Cue.app/Contents/MacOS/run --selftest=llama_server_health
Cue.app/Contents/MacOS/run --selftest=llama_server_import
The CI step runs these post-build.
Windows — PyInstaller + Inno Setup¶
python scripts/build_installer.py
Or step-by-step:
scripts\build_windows.bat # PyInstaller .exe
python scripts/build_installer.py # Inno Setup .msi
PyInstaller produces a one-folder Cue/ distribution that includes
the bundled llama-server.exe under Cue\bin\llama-b<tag>\. Inno
Setup (scripts/release/cue_setup.iss) wraps it into a Windows
installer that registers the app, sets up the Start Menu shortcut,
and creates the data dir on first run.
Code signing on Windows is currently TBD — the installer can be signed via SignTool with an Authenticode certificate, but the default build skips this step.
Bundled llama-server¶
The on-device digest backend uses an external multimodal inference
binary, not llama-cpp-python. See
On-device vision for the rationale.
The build matrix:
- macOS arm64 / x64 —
llama-<tag>-bin-macos-{arm64,x64}.tar.gzrelease artifact fromggml-org/llama.cpp. - Windows x64 —
llama-<tag>-bin-win-cpu-x64.ziprelease artifact.
scripts/build_nuitka.sh and scripts/build_installer.py download
+ verify + extract these binaries into the bundle. The pinned
release tag is updated by editing the manifest in scripts/.
CI integration¶
.github/workflows/ci.yml runs ruff lint + format on every push.
Application release builds are not run in CI (they need keychain
access and an Apple Developer cert that isn't in CI).
.github/workflows/docs.yml is a separate workflow:
- On every PR + main push, runs
uv pip install -e ".[docs]"anduv run --no-sync mkdocs build --strict. Catches broken nav, missing anchors, andmkdocstringsimport regressions before the deploy. - On main pushes, additionally invokes
cloudflare/wrangler-action@v3with theCLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_IDrepo secrets to push the builtsite/to thecueCloudflare Pages project (pages deploy site --project-name=cue --branch=main). - Submodule fetch is intentionally off in this workflow — docs build doesn't need ocap or owa, and Cloudflare's git-connect flow couldn't read our private vendor submodules anyway. That's the whole reason deploys go through GitHub Actions instead of Cloudflare's Git integration.
Public docs URL: https://cue-aif.pages.dev/.
Version¶
Single source of truth: pyproject.toml → version. Also update
_VERSION in main.py and the README footer.
See also¶
- Dev environment — local dev workflow.
- Submodules & vendoring.
- Cross-platform rule.
- On-device vision.