▄▀█ █▄ █ ▀█▀ █▀█ █▄ █
█▀█ █ ▀█ █ █▄█ █ ▀█
Desktop App
The official Electron desktop app for Anton — MindsDB's autonomous AI coworker. Cross-platform (macOS + Windows), auto-installs Anton on first run, and provides a chat-based cowork UI backed by a FastAPI sidecar, with Minds integration.
# Install dependencies
npm install
# Build everything (main + renderer)
npm run build
# Run locally
npm startnpm run devnpm run dev:debugThis opens the Electron app against the Vite dev server and auto-opens Chromium DevTools in a detached window.
This runs three processes concurrently:
tsc --watchfor main processvite devfor renderer (port 5173)- Electron with
VITE_DEV=1flag
When testing a packaged build (npm run pack), the DEV_MODE variable in ~/.anton/.env controls which renderer the app loads:
| Value | Behavior |
|---|---|
live |
Load from Vite dev server (localhost:5173) — requires npm run dev:renderer running separately |
full |
Load the bundled renderer only — skips OTA cache entirely |
| (unset) | Production mode — loads OTA-cached UI if available, otherwise bundled |
To set it, add DEV_MODE=full (or live) to ~/.anton/.env. Remove the line to return to production behavior. When DEV_MODE is set, the OTA update check is skipped entirely.
Tip: If you build the app and it looks outdated, the OTA cache may be serving an older published bundle. Either set
DEV_MODE=fullto bypass it, or clear the cache:rm -rf ~/Library/Application\ Support/anton/ui-cache/current
The cowork SPA also runs as a plain web app, served by the same FastAPI backend. The renderer is shell-agnostic — there is one source tree, one component library, and two entrypoints.
npm run dev:webThis boots both processes:
- The Anton FastAPI sidecar on
127.0.0.1:26866(using youruv tool install antoninterpreter — same as the Electron path). - Vite dev server on
localhost:5173, withBUILD_TARGET=web.
The dev server opens at http://localhost:5173/ (a small Vite
middleware rewrites / → /index-web.html so the bare URL is
canonical). API calls hit the FastAPI sidecar via Vite's
/v1 and /health proxies. Press Ctrl-C once for a clean
shutdown — vite quiesces first, then the python child.
If you haven't installed Anton yet, dev:web will print:
✗ Anton Python interpreter not found at ~/.local/share/uv/tools/anton/bin/python.
Run `uv tool install anton` first, then re-run `npm run dev:web`.
npm run build:webOutputs to dist/renderer-web/ (separate from dist/renderer/ which is
the Electron build). Drop this directory behind any static-file server
and point its /v1 requests at a running Anton FastAPI process.
The cowork tree (src/renderer/cowork/) never touches
window.antontron directly. All host-bridge access goes through
src/renderer/platform/host.ts, which exposes:
| Method | Electron | Web |
|---|---|---|
getPlatform() / isMac() |
'darwin' | 'win32' | 'linux' |
'web' / false |
getApiOrigin() |
http://127.0.0.1:26866 |
window.location.origin |
openExternal(url) |
Electron shell.openExternal | window.open(url, '_blank') |
openPath / showItemInFolder / trashItem |
OS shell | { ok: false, reason: 'unsupported' } |
serverInfo / serverStart / serverStop |
IPC to main | static {running: true, …} |
oauthConnect(...) |
IPC PKCE loopback flow | inline error (redirect-based OAuth not yet wired) |
Affordances that depend on Electron-only bridge calls (server pill +
power button in the sidebar, "Open in OS" / "Show in Finder" /
"Move to Trash" buttons in the artifact views, the
ServerOfflineHelpModal) are hidden when host.isWeb is true.
src/renderer/
index.html # Electron entry (loads main.tsx)
index-web.html # Web entry (loads web-main.tsx)
main.tsx # Electron entry: App.tsx → CoworkApp (with onboarding gates)
web-main.tsx # Web entry: cowork SPA directly (no onboarding gates)
platform/host.ts # Shell abstraction (the only sanctioned bridge surface)
cowork/ # The shared SPA — never imports window.antontron
vite.config.ts branches on BUILD_TARGET=web: when set, rollupOptions.input
points at index-web.html and outDir becomes dist/renderer-web/. When
unset (the Electron path), behavior is byte-identical to before.
src/
main/ # Electron main process (Node.js)
index.ts # Window creation, IPC handlers, menu
installer.ts # Auto-installer for Anton CLI (uv + git + Xcode CLT)
server-process.ts # FastAPI sidecar lifecycle (start/stop/health)
ui-updater.ts # OTA UI update system (fetch, verify, cache, rollback)
preload.ts # contextBridge — exposes antontron API to renderer
renderer/ # React UI (bundled by Vite)
App.tsx # App flow: loading -> setup -> onboarding -> cowork
CoworkApp.tsx # Main chat-based cowork shell
pages/
Setup.tsx # Install wizard with step progress
Onboarding.tsx # LLM provider selection (Anthropic / Minds)
cowork/ # Shared SPA — never imports window.antontron
platform/host.ts # Shell abstraction (the only sanctioned bridge surface)
styles.css # Full dark theme
global.d.ts # TypeScript types for window.antontron API
shared/
ipc-channels.ts # All IPC channel constants
assets/
icon.png / icon.icns # App icon (gradient cyan-to-purple "A")
-
FastAPI sidecar: The Electron main process manages a bundled Python FastAPI server on
127.0.0.1:26866. The renderer communicates with Anton exclusively through this HTTP API — there is no PTY or terminal emulator. -
Minds integration: The GUI replicates Anton's
/connectflow — lists minds via REST API, handles datasource selection (normalizes string/object refs), writes the same env vars to~/.anton/.env, and auto-restarts Anton to pick up new config. -
OTA UI updates: The Electron shell ships rarely, but the React UI updates frequently via GitHub Releases. On every boot, the main process checks a static
latest.jsonon GitHub Pages (no API rate limits). In auto mode, new bundles are downloaded, SHA-256 verified, and applied silently — the app reloads with the new UI. In manual mode (the default), a green banner appears in the sidebar and the user clicks "Install" to apply. The update preference is persisted asUI_UPDATE_MODEin~/.anton/.envand configurable in Settings → Updates.
All channels defined in src/shared/ipc-channels.ts:
| Channel | Direction | Purpose |
|---|---|---|
install:check |
invoke | Check if Anton CLI is installed |
install:start |
invoke | Run the installer |
install:log/progress/done/error |
send | Installer status events |
server:get-info/start/stop/toggle |
invoke | FastAPI sidecar lifecycle |
server:get-diagnostics |
invoke | Last error, exit code, recent log tail |
oauth:connect |
invoke | PKCE OAuth loopback flow |
settings:read/save/check-configured/validate |
invoke | Settings & API key management |
ui:update-check |
invoke | Check for OTA UI updates |
ui:update-apply |
invoke | Download and apply a pending UI update |
ui:update-status |
send | Update status events (available/reloading) |
app:get-platform/ui-version/open-external |
invoke | Platform info, open URLs |
shell:open-path/show-item-in-folder/trash-item |
invoke | OS shell operations |
The GUI provides a visual /connect flow:
- If LLM provider is Minds (from onboarding), credentials are pre-filled
- Lists available minds via
GET /api/v1/minds/ - Handles datasource selection (auto-selects if only one)
- Fetches engine type via
GET /api/v1/datasources - Writes to
~/.anton/.env:ANTON_MINDS_API_KEYANTON_MINDS_URLANTON_MINDS_MIND_NAMEANTON_MINDS_DATASOURCEANTON_MINDS_DATASOURCE_ENGINEANTON_MINDS_SSL_VERIFY
- Writes mind's system prompt to project cortex
- Auto-restarts Anton to pick up new config
- Node.js 18+
- npm
- For macOS signing: Apple Developer account + certificates
- For Windows signing: EV code signing certificate
# Build unsigned DMG (universal: x64 + arm64)
npm run dist:mac
# Output: release/Anton-{version}-universal.dmg# Build NSIS installer (x64, recommended)
npm run dist:win
# Output: release/Anton-Setup-{version}.exe# Alias for local/manual Windows release
npm run release:win:local
# If you explicitly need x64 + arm64
npm run dist:win:allNote: Production Windows installers are built by
.github/workflows/prod-build-installer.yml, which is fired automatically by the auto-release flow described in Releasing. Don't pushv*tags manually — bump"version"inpackage.jsonand merge tomaininstead.
You need two certificates:
- Developer ID Application — signs the app binary
- Developer ID Installer — signs the DMG/pkg (optional but recommended)
# Verify your certificates are installed
security find-identity -v -p codesigning
# Should show: "Developer ID Application: Your Org (TEAMID)"# Apple ID credentials for notarization
export APPLE_ID="your@email.com"
export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx" # Generate at appleid.apple.com
export APPLE_TEAM_ID="YOUR_TEAM_ID"
# OR use API key (recommended for CI)
export APPLE_API_KEY_ID="XXXXXXXXXX"
export APPLE_API_KEY_ISSUER="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export APPLE_API_KEY="/path/to/AuthKey_XXXXXXXXXX.p8"Where to export them:
# Option A: Current terminal session only (recommended for local/manual release)
export APPLE_ID="your@email.com"
export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx"
export APPLE_TEAM_ID="YOUR_TEAM_ID"
npm run dist:mac:dmg-custom# Option B: Persist in zsh profile (loads in every new terminal)
echo 'export APPLE_ID="your@email.com"' >> ~/.zshrc
echo 'export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx"' >> ~/.zshrc
echo 'export APPLE_TEAM_ID="YOUR_TEAM_ID"' >> ~/.zshrc
source ~/.zshrc# Verify env vars are present
env | rg '^APPLE_'mac:
hardenedRuntime: true
gatekeeperAssess: false
entitlements: build/entitlements.mac.plist
entitlementsInherit: build/entitlements.mac.plist
afterSign: scripts/notarize.jsbuild/entitlements.mac.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>These entitlements are required because Electron uses JIT and dynamic linking.
scripts/notarize.js:
const { notarize } = require("@electron/notarize");
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== "darwin") return;
const appName = context.packager.appInfo.productFilename;
console.log("Notarizing...");
await notarize({
// Use Apple ID auth:
tool: "notarytool",
appBundleId: "com.anton.app",
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
teamId: process.env.APPLE_TEAM_ID,
// OR use API key auth (uncomment):
// appleApiKey: process.env.APPLE_API_KEY,
// appleApiKeyId: process.env.APPLE_API_KEY_ID,
// appleApiIssuer: process.env.APPLE_API_KEY_ISSUER,
});
console.log("Notarization complete.");
};If @electron/notarize is missing in your local install:
npm install --save-dev @electron/notarizenpm run dist:mac
# electron-builder will: sign -> notarize -> staple -> create DMG# Check if app is signed
codesign -dv --verbose=4 "release/mac-universal/Anton.app"
# Check notarization status
xcrun stapler validate "release/Anton-0.1.0-universal.dmg"
# If "Developer ID" identity not found, open Keychain Access
# and verify the certificate is in "login" keychain, not expiredMost EV certificates come on a hardware USB token (SafeNet, YubiKey).
# Set env vars
export CSC_LINK="/path/to/certificate.pfx" # or .p12
export CSC_KEY_PASSWORD="your-password"
# For USB tokens (SafeNet eToken):
export CSC_LINK="" # empty — electron-builder finds it via signtool
export WIN_CSC_LINK=""
# Build
npm run dist:winMicrosoft's cloud signing service — recommended for CI/CD.
- Set up Azure Trusted Signing in Azure Portal
- Install the signing tool:
dotnet tool install --global AzureSignTool- Add to
electron-builder.yml:
win:
signingHashAlgorithms: [sha256]
sign: scripts/azure-sign.js- Create
scripts/azure-sign.js:
exports.default = async function sign(configuration) {
const { execSync } = require("child_process");
const filePath = configuration.path;
execSync(
`AzureSignTool sign \
-kvu "${process.env.AZURE_KEY_VAULT_URI}" \
-kvi "${process.env.AZURE_CLIENT_ID}" \
-kvs "${process.env.AZURE_CLIENT_SECRET}" \
-kvt "${process.env.AZURE_TENANT_ID}" \
-kvc "${process.env.AZURE_CERT_NAME}" \
-tr http://timestamp.digicert.com \
-td sha256 \
"${filePath}"`,
{ stdio: "inherit" },
);
};# PowerShell — create a self-signed cert
$cert = New-SelfSignedCertificate -Subject "CN=Anton Dev" -Type CodeSigningCert -CertStoreLocation Cert:\CurrentUser\My
Export-PfxCertificate -Cert $cert -FilePath anton-dev.pfx -Password (ConvertTo-SecureString -String "password" -Force -AsPlainText)export CSC_LINK="anton-dev.pfx"
export CSC_KEY_PASSWORD="password"
npm run dist:winSelf-signed apps will still trigger SmartScreen warnings. Only EV certs or Azure Trusted Signing build SmartScreen reputation.
The desktop shell (Electron main process) handles IPC, the FastAPI sidecar, and native OS integration — it changes rarely. The renderer (React UI) is where most iteration happens. Anton Desktop ships with an OTA update system that lets you push UI updates to every installed app without shipping a new .dmg or .exe.
Because mindsdb/antontron is private, the app can't fetch releases from it without baked-in tokens. Instead, OTA assets are published to a separate public repo: mindsdb/antontron-releases.
┌─────────────────────────────────────┐ ┌──────────────────────────────────┐
│ mindsdb/antontron (PRIVATE) │ │ mindsdb/antontron-releases │
│ │ │ (PUBLIC) │
│ source code lives here │ │ │
│ │ push │ GitHub Releases: │
│ .github/workflows/publish-ui.yml ──┼───────▶│ ui-v1.2.0/ui-bundle.tar.gz │
│ │ │ │
│ │ │ GitHub Pages (gh-pages branch): │
│ │ │ latest.json │
└─────────────────────────────────────┘ └──────────────────────────────────┘
▲
│ HTTPS (no auth)
│
┌────────────┴─────────────┐
│ Anton Desktop App │
│ (every user's machine) │
└──────────────────────────┘
- Code is merged to
main(or aui-v*tag is pushed) - The
publish-uiworkflow in the private repo builds the renderer - It creates a
.tar.gzbundle, computes a SHA-256 checksum - Using a
RELEASES_TOKEN, it pushes the bundle as a GitHub Release and updateslatest.jsonon GitHub Pages — both on the publicantontron-releasesrepo - Every Anton Desktop launch, the app fetches
https://mindsdb.github.io/antontron-releases/latest.json(static file, no auth, no API rate limits) - If a newer version exists, it downloads the bundle, verifies the SHA-256 checksum, and caches it
- Next launch loads the updated UI — zero user interaction required
The workflow triggers automatically on three events:
| Trigger | When | Version format | Example |
|---|---|---|---|
Push to main |
Any merge that changes src/renderer/, src/shared/, or package.json |
{pkg.version}-{sha} |
1.0.1-a3b4c5d |
| Tag push | git tag ui-v1.2.0 && git push origin ui-v1.2.0 |
Clean version from tag | 1.2.0 |
| Manual dispatch | Actions UI → Run workflow | Whatever you enter (or pkg.version + sha if empty) | 1.2.0 |
This means every merge to main that touches UI files automatically deploys to all users. No manual tagging required for day-to-day work. Use explicit tags (ui-v*) for milestone releases.
The workflow also checks if the version is already published and skips duplicate releases — safe to re-run.
git tag ui-v1.2.0
git push origin ui-v1.2.0- Go to Actions → Publish UI Bundle
- Click "Run workflow" (top right)
- Branch:
main - Version:
1.2.0(leave empty to auto-generate from package.json) - Click the green "Run workflow" button
If your PR changes anything in src/renderer/, src/shared/, or package.json, merging it will automatically publish a new UI version.
After the workflow completes:
- Manifest: https://mindsdb.github.io/antontron-releases/latest.json — should show the new version, download URL, and SHA-256
- Release: https://github.com/mindsdb/antontron-releases/releases — should show the new
ui-v*release withui-bundle.tar.gzattached - In the app: Launch Anton Desktop, then check Anton → About Anton — shows
1.0.1 (UI: 1.2.0)when OTA is active
- Every bundle is integrity-checked with SHA-256 before extraction
- Checksum mismatch → update is silently discarded, app loads last known good UI
- Previous version is kept on disk for automatic rollback if the new UI fails to load
- All downloads over HTTPS from GitHub's CDN
- The
RELEASES_TOKENonly has write access to the publicantontron-releasesrepo — source code in the private repo is never exposed
App starts
├─ If DEV_MODE is set → load Vite dev server (live) or bundled renderer (full), skip OTA
├─ Load cached UI (instant, no network needed)
│ └─ Falls back to bundled renderer if no cache exists
└─ After renderer loads (did-finish-load + 1.5 s):
└─ Background: fetch latest.json from GitHub Pages
├─ If offline or up to date → done
└─ If new version available:
├─ Auto mode → download → verify SHA-256 → swap → reload
└─ Manual mode → send "update-available" to renderer → sidebar banner
└─ User clicks "Install" → download → verify → swap → reload
The app never blocks on a network request — it always loads immediately from cache or bundled files. The OTA check waits for the renderer to finish loading before running to avoid IPC race conditions.
On disk (Electron userData directory):
{userData}/ui-cache/
version.json # { "version": "1.2.0" }
current/ # Active renderer bundle (index.html + assets)
previous/ # Rollback copy of the prior version
On GitHub (mindsdb/antontron-releases):
gh-pages branch:
latest.json # { "version": "1.2.0", "url": "...", "sha256": "..." }
GitHub Releases:
ui-v1.2.0/
ui-bundle.tar.gz # The renderer build output
Anton Desktop uses an automated release flow. The single source of truth for the package version is package.json ("version"). Every build workflow reads this field, and the prod upload job (upload-installer-to-s3.yml) asserts it matches the release tag before publishing to S3.
- Open a PR that bumps
"version"inpackage.json(e.g.2.0.4→2.0.5). Follow SemVer. - Get it reviewed and merge to
main. - That's it. On merge,
.github/workflows/release.ymlautomatically:- Creates the matching git tag (
v2.0.5). - Publishes a GitHub release with auto-generated notes.
- The
v*tag push triggersprod-build-installer.yml, which builds + signs + uploads the macOS.pkgand Windows.exetos3://anton-installer/anton/{mac,windows}/and serves them athttps://downloads.mindsdb.com/anton/....
- Creates the matching git tag (
- Don't create GitHub releases manually. The
v*tag namespace is locked via a repo ruleset — only the release workflow can create them. Manual attempts will be rejected by GitHub. - Don't push
v*tags directly. Same protection applies. - Don't edit
"version"inpackage.jsonoutside a dedicated bump PR. Keep version bumps small and reviewable so the auto-release diff is easy to audit.
Anything under .github/ is owned by @mindsdb/devops via CODEOWNERS. PRs touching workflows, actions, or release configuration require their review before merge.
If you genuinely need to release outside the normal flow (e.g. an admin hotfix), coordinate with @mindsdb/devops to bypass the tag ruleset. The prod upload job's package.json-vs-tag guard at upload-installer-to-s3.yml will still verify the release tag matches package.json "version" and fail loudly on mismatch.
The macOS (.pkg) and Windows (.exe) installers are built on GitHub-hosted runners (needed for Apple notarization / SSL.com signing) and then uploaded to S3 from the self-hosted mdb-prod pod. There are three flavors of build — preview, stable, and prod — distinguished only by when they run and the S3 path (and therefore the public downloads.mindsdb.com path) they land on.
| Flavor | Trigger | What builds | S3 destination |
|---|---|---|---|
| preview | PR with signed-macos-pkg label → macOS only. PR with signed-windows-ev label → Windows only. |
anton-{version}-preview-{sha}.pkg / .exe |
s3://anton-installer/anton/{mac,windows}/previews/ |
| stable | Push to main |
Both platforms, anton-{version}-stable-{sha}.pkg / .exe |
s3://anton-installer/anton/{mac,windows}/snapshots/ |
| prod | Push tag v* |
Both platforms, anton-{version}.pkg / .exe |
s3://anton-installer/anton/{mac,windows}/anton-{version}.{pkg,exe} and anton-latest.{pkg,exe} |
A PR without the matching signed-* label does nothing — no build, no upload.
Prod is gated by a version check: the first thing the upload job does when build_kind == prod is assert that package.json version equals the release tag (with the leading v stripped). Mismatch → workflow fails before anything reaches S3.
The bucket is anton-installer in us-east-1 (separate from the other anton bucket, which is in us-east-2). It is private — no public reads, no public ACLs, no presigned URLs for regular downloads. Everything is served through the CloudFront distribution described below. AWS credentials are not configured as GitHub secrets — they come from the mdb-prod pod's IAM role, the same way release-gui-to-production.yml works in the GUI repo. The role must have s3:PutObject on arn:aws:s3:::anton-installer/anton/*.
s3://anton-installer/
anton/
mac/
anton-{version}.pkg # prod — versioned
anton-latest.pkg # prod — always points at the most recent release
previews/anton-{version}-preview-{sha}.pkg
snapshots/anton-{version}-stable-{sha}.pkg
windows/
anton-{version}.exe
anton-latest.exe
previews/anton-{version}-preview-{sha}.exe
snapshots/anton-{version}-stable-{sha}.exe
No sidecar .sha256 files are published — the .pkg is notarized by Apple and the .exe is EV-signed via SSL.com, so OS-level signature verification is the integrity guarantee.
Lifecycle tip: set bucket lifecycle rules to auto-expire objects under
previews/(e.g. 14 days) andsnapshots/(e.g. 60 days) to keep costs bounded. Prod objects have no expiration.
End users never hit S3 directly. The anton-installer bucket is fronted by a CloudFront distribution aliased to https://downloads.mindsdb.com, which is how all installers are distributed publicly.
Infrastructure:
- CloudFront + ACM + S3 OAC live in
terraform/newprod/us-east-1/anton/cloudfront.tf, which also defines the bucket policy / public-access-block that keep the bucket itself private and reachable only via CloudFront's Origin Access Control. - The bucket resource is in
terraform/newprod/us-east-1/anton/s3.tf. - The CloudFront domain name is published via
terraform/newprod/us-east-1/anton/outputs.tf(cloudfront_downloads_domain_name) and consumed by the Cloudflare stack. - DNS — the
downloads.mindsdb.comCNAME and the ACM validation records — is managed interraform/newprod/global/cloudflare/downloads.mindsdb.com-domain.tf.
CloudFront behavior:
- Path mapping is 1:1 — CloudFront does not rewrite the key, so the S3 key
anton/mac/anton-latest.pkgis reachable athttps://downloads.mindsdb.com/anton/mac/anton-latest.pkg. - Viewer-protocol policy is
redirect-to-https. GET /is rewritten to a 302 redirect tohttps://mindsdb.comby thedownloads-root-redirectCloudFront Function (viewer-request).GET /<missing key>(S3 403/404) is rewritten to a 302 redirect tohttps://mindsdb.comby thedownloads-error-redirectCloudFront Function (viewer-response). In other words, the bucket never leaks its existence — unknown paths bounce to the marketing site instead of returning an XML error.- Default cache TTL is 1 hour, max 24 hours. Compression is enabled. No query strings or cookies are forwarded.
Public URL layout:
https://downloads.mindsdb.com/
anton/
mac/
anton-{version}.pkg # prod — versioned
anton-latest.pkg # prod — always the newest release
previews/anton-{version}-preview-{sha}.pkg
snapshots/anton-{version}-stable-{sha}.pkg
windows/
anton-{version}.exe
anton-latest.exe
previews/anton-{version}-preview-{sha}.exe
snapshots/anton-{version}-stable-{sha}.exe
Stable download links to share externally:
- macOS latest: https://downloads.mindsdb.com/anton/mac/anton-latest.pkg
- Windows latest: https://downloads.mindsdb.com/anton/windows/anton-latest.exe
The upload-installer-to-s3.yml workflow prints both the s3:// URI and the https://downloads.mindsdb.com/... URL for every object it uploads in its GitHub step summary, so PRs and releases have a clickable public URL in the Actions run.
Cache invalidations: because
anton-latest.{pkg,exe}is overwritten on every prod release, CloudFront may serve the stale copy for up to thedefault_ttl(currently 1 hour). If a release needs to be visible immediately, create an invalidation for/anton/mac/anton-latest.pkgand/or/anton/windows/anton-latest.exe. Versioned URLs (anton-{version}.pkg) are immutable and never need invalidation.
The layout mirrors the MindsDB dev-/staging-/prod- pattern: one small top-level file per trigger, shared work in workflow_call files.
| Workflow | Kind | Trigger | What it does |
|---|---|---|---|
dev-build-installer.yml |
Instance | pull_request |
Label-gates per platform and wires to the build + upload called workflows with build_kind: preview |
staging-build-installer.yml |
Instance | Push to main |
Builds both platforms with build_kind: stable |
prod-build-installer.yml |
Instance | Push tag v* |
Builds both platforms with build_kind: prod (upload does the version vs tag check) |
build-macos-pkg.yml |
Called (workflow_call) |
— | Builds + signs + notarizes the .pkg on macos-latest, renames to the final artifact name, uploads as GitHub artifact |
build-windows-installer.yml |
Called (workflow_call) |
— | Builds + SSL.com-signs + verifies the .exe on windows-latest, renames, uploads as GitHub artifact |
upload-installer-to-s3.yml |
Called (workflow_call) |
— | Runs on mdb-prod, downloads the GitHub artifact, runs the prod version check, aws s3 cp to the correct path |
publish-ui.yml |
Standalone | Push to main (renderer changes), ui-v* tag, manual |
Publishes the renderer bundle to mindsdb/antontron-releases (unrelated to installer flow) |
The build workflows expose an artifact_name output; the instance workflows pass it through to the upload workflow so the artifact name is the single source of truth and no filename is computed twice.
Configured in antontron → Settings → Secrets → Repository secrets.
Apple signing / notarization (used by build-macos-pkg.yml):
APPLE_DEV_ID_APP_CERT_B64APPLE_DEV_ID_APP_CERT_PASSWORDAPPLE_DEV_ID_INSTALLER_CERT_B64APPLE_DEV_ID_INSTALLER_CERT_PASSWORDAPPLE_IDAPPLE_APP_SPECIFIC_PASSWORDAPPLE_TEAM_IDAPPLE_INSTALLER_IDENTITY(example:Developer ID Installer: Your Org (TEAMID))
Windows signing via SSL.com eSigner (used by build-windows-installer.yml):
SSL_USERNAMESSL_PASSWORDSSL_CREDENTIAL_IDSSL_TOTP_SECRET
OTA UI publishing (used by publish-ui.yml):
RELEASES_TOKEN— fine-grained PAT scoped tomindsdb/antontron-releaseswith Contents (read/write) + Metadata (read)
No AWS secrets. The upload job runs on
mdb-prodand picks up AWS credentials from the pod's IAM role. The role must haves3:PutObjectonarn:aws:s3:::anton-installer/anton/*.
This section covers the one-time setup for publish-ui.yml only — it's independent of the installer flow above.
- Create
mindsdb/antontron-releasesas a public repo. It only holds release assets andlatest.json— no source code. - Create the
RELEASES_TOKEN:- GitHub → Settings → Developer settings → Fine-grained tokens
- Name:
antontron-releases-deploy - Repository access: only
mindsdb/antontron-releases - Permissions: Contents (read/write), Metadata (read)
- Save the token as
RELEASES_TOKENin antontron → Settings → Secrets → Actions.
- Enable GitHub Pages on
antontron-releases: Settings → Pages → Source "Deploy from a branch" → Branchgh-pages// (root). Thegh-pagesbranch is created automatically by the first workflow run. - Verify with:
curl https://mindsdb.github.io/antontron-releases/latest.jsonThe app icon is a gradient cyan-to-purple "A" on a dark background.
# Generate icon.png and icon.icns from the SVG
node scripts/generate-icon.jsSource SVG is in assets/icon.svg. The script renders it to PNG then uses sips + iconutil to create the .icns for macOS.
For Windows, electron-builder auto-converts icon.png to .ico.
These are written to ~/.anton/.env by the app and read by Anton at startup:
| Variable | Source | Purpose |
|---|---|---|
ANTON_ANTHROPIC_API_KEY |
Onboarding | Anthropic API key |
ANTON_OPENAI_API_KEY |
Onboarding | Minds/OpenAI-compatible API key |
ANTON_OPENAI_BASE_URL |
Onboarding | Minds server URL (as OpenAI base) |
ANTON_MINDS_API_KEY |
Minds panel | Minds API key for datasources |
ANTON_MINDS_URL |
Minds panel | Minds server URL |
ANTON_MINDS_MIND_NAME |
Minds panel | Selected mind name |
ANTON_MINDS_DATASOURCE |
Minds panel | Selected datasource |
ANTON_MINDS_DATASOURCE_ENGINE |
Minds panel | Datasource engine type |
ANTON_MINDS_SSL_VERIFY |
Minds panel | SSL cert verification (true/false) |
ANTON_PLANNING_MODEL |
Settings | Model for planning tasks |
ANTON_CODING_MODEL |
Settings | Model for coding tasks |
ANTON_MEMORY_MODE |
Settings | Memory mode (autopilot/copilot/off) |
DEV_MODE |
Manual | Renderer source override for developers (live = Vite dev server, full = bundled only, unset = production with OTA) |
UI_UPDATE_MODE |
Settings | OTA update behavior (auto = apply silently, manual = show banner; default manual) |
# Make sure both main and renderer are built
npm run build
# Check if Vite output exists
ls dist/renderer/index.htmlThe packaged .app doesn't inherit shell PATH. The sidecar is spawned via the uv tool install interpreter, so ensure Anton is installed via uv tool install anton. If issues persist, check that the Python interpreter at ~/.local/share/uv/tools/anton/bin/python exists.
# Remove quarantine attribute (dev only)
xattr -cr "/Applications/Anton.app"| Layer | Tech |
|---|---|
| Framework | Electron 34 |
| Renderer | React 19 + TypeScript + Vite 6 |
| Backend | FastAPI (Python, bundled sidecar) |
| Markdown | marked 17 |
| Packaging | electron-builder 25 |
| Styling | Tailwind CSS + custom theme |
Built by MindsDB. Anton is the autonomous AI coworker.