FabricFabric
Development

Building for macOS

Step-by-step instructions for building and publishing the Fabric Agents macOS app (DMG + ZIP) from a Mac machine.

macOS builds must be done on a Mac — electron-builder requires macOS to produce .dmg and .zip artifacts, and code signing requires the Apple toolchain. This guide walks through the full process: build, checksum, upload to Cloudflare R2, and deploy.

For full cross-platform releases, use the GitHub Actions workflow:

  • .github/workflows/release-electron-cloudflare.yml

It builds:

  • Windows on windows-latest (native EXE resources/icon)
  • Linux on ubuntu-latest
  • macOS on macos-latest

Then it assembles electron/latest, regenerates latest.json, uploads to R2, and deploys Cloudflare.

Use this macOS guide as the manual fallback when you need to build/publish from a local Mac.

Prerequisites

Required

  • macOS — any version supported by Xcode 15+
  • Xcode Command Line Tools
    xcode-select --install
  • Bun — the monorepo package manager
    curl -fsSL https://bun.sh/install | bash
  • Wrangler — Cloudflare CLI for R2 uploads and worker deploys
    npm install -g wrangler
    Then authenticate:
    wrangler login

Optional (for signed + notarized builds)

Unsigned builds work for testing. For public distribution you need:

  • Apple Developer Program membership
  • A Developer ID Application certificate in your Keychain
  • An app-specific password for your Apple ID (for notarization)

Set these in .env at the repo root (or export them in your shell):

APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)"
APPLE_ID="you@example.com"
APPLE_TEAM_ID="XXXXXXXXXX"
APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx"

Clone and Install

git clone <repo-url>
cd fabric-agents
bun install

Build

The build script handles everything: downloading the Bun runtime, copying the SDK and interceptors, compiling the app, and packaging with electron-builder.

arm64 (Apple Silicon)

bash apps/electron/scripts/build-dmg.sh arm64

x64 (Intel)

bash apps/electron/scripts/build-dmg.sh x64

Both architectures produce two artifacts each — a DMG (installer) and a ZIP (portable, used by the install script):

ArchDMGZIP
arm64apps/electron/release/Fabric-Agent-arm64.dmgapps/electron/release/Fabric-Agent-arm64.zip
x64apps/electron/release/Fabric-Agent-x64.dmgapps/electron/release/Fabric-Agent-x64.zip

Typical build time is 5–10 minutes per architecture. The script prints progress at each step.

Build script options

bash apps/electron/scripts/build-dmg.sh [arm64|x64] [--upload] [--latest] [--script]
FlagDescription
arm64 | x64Target architecture (default: arm64)
--uploadUpload artifacts to S3 after building (requires S3 env vars)
--latestAlso update the electron/latest path on S3 (requires --upload)
--scriptAlso upload install-app.sh (requires --upload)

Compute Checksums

The install script verifies each download against a SHA-512 checksum stored in latest.json. Compute checksums for both ZIPs:

cd apps/electron/release

python3 - <<'EOF'
import hashlib, base64

files = {
    "darwin-arm64": "Fabric-Agent-arm64.zip",
    "darwin-x64":   "Fabric-Agent-x64.zip",
}
for key, filename in files.items():
    with open(filename, "rb") as f:
        digest = hashlib.sha512(f.read()).digest()
    print(f"{key}: {base64.b64encode(digest).decode()}")
EOF

Note the two base64 strings — you will need them in the next step.

Update latest.json

Open apps/fabric-agents-worker/deploy/electron/latest/latest.json and update the darwin-arm64 and darwin-x64 entries with the URLs and checksums you just computed. Also update published_at to the current UTC time.

{
  "version": "0.5.1",
  "published_at": "2026-03-01T20:00:00Z",
  "assets": {
    "darwin-arm64": {
      "url": "https://agents.fabric.pro/electron/latest/Fabric-Agent-arm64.zip",
      "sha512": "<arm64 base64 checksum>"
    },
    "darwin-x64": {
      "url": "https://agents.fabric.pro/electron/latest/Fabric-Agent-x64.zip",
      "sha512": "<x64 base64 checksum>"
    },
    "linux-x64": {
      "url": "https://agents.fabric.pro/electron/latest/Fabric-Agent-x64.AppImage",
      "sha512": "..."
    },
    "windows-x64": {
      "url": "https://agents.fabric.pro/electron/latest/Fabric-Agent-win32-x64.zip",
      "sha512": "..."
    }
  }
}

Keep the existing linux-x64 and windows-x64 entries unchanged.

Upload to Cloudflare R2

First, export your Cloudflare account ID and an API token with R2 write permission:

export CLOUDFLARE_ACCOUNT_ID="<your-cloudflare-account-id>"
export CLOUDFLARE_API_TOKEN="<your-r2-scoped-api-token>"

Ask a repo maintainer for the Fabric Agents account ID and a scoped token if you don't have one. Never paste these into public channels or commit them to the repo.

Upload the two ZIPs and the updated manifest:

RELEASE_DIR="apps/electron/release"
MANIFEST="apps/fabric-agents-worker/deploy/electron/latest/latest.json"

# Upload arm64 ZIP
npx wrangler r2 object put \
  fabric-agents-releases/electron/latest/Fabric-Agent-arm64.zip \
  --file "$RELEASE_DIR/Fabric-Agent-arm64.zip" \
  --content-type application/zip \
  --remote

# Upload x64 ZIP
npx wrangler r2 object put \
  fabric-agents-releases/electron/latest/Fabric-Agent-x64.zip \
  --file "$RELEASE_DIR/Fabric-Agent-x64.zip" \
  --content-type application/zip \
  --remote

# Upload updated manifest
npx wrangler r2 object put \
  fabric-agents-releases/electron/latest/latest.json \
  --file "$MANIFEST" \
  --content-type application/json \
  --remote

You can also upload the DMGs if you want them available for direct download:

npx wrangler r2 object put \
  fabric-agents-releases/electron/latest/Fabric-Agent-arm64.dmg \
  --file "$RELEASE_DIR/Fabric-Agent-arm64.dmg" \
  --content-type application/octet-stream \
  --remote

npx wrangler r2 object put \
  fabric-agents-releases/electron/latest/Fabric-Agent-x64.dmg \
  --file "$RELEASE_DIR/Fabric-Agent-x64.dmg" \
  --content-type application/octet-stream \
  --remote

Deploy the Worker

The worker serves the static latest.json copy from its deploy/ assets directory. Deploying it makes the new manifest live at https://agents.fabric.pro/electron/latest/latest.json.

cd apps/fabric-agents-worker
npx wrangler deploy

(This reuses the CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN you exported above.)

Verify

Confirm the live manifest reflects the new checksums:

curl -s https://agents.fabric.pro/electron/latest/latest.json | python3 -m json.tool

Then test the install script on macOS:

bash <(curl -fsSL https://agents.fabric.pro/install-app.sh)

Cross-Platform Publish (Mac + Linux/Windows)

For a full release, publish all three platform artifacts into electron/latest:

  1. macOS (built on Mac):
    • Fabric-Agent-arm64.zip
    • Fabric-Agent-x64.zip
  2. Linux (can be built on Linux):
    • Fabric-Agent-x86_64.AppImage
  3. Windows (can be produced on Linux as portable ZIP):
    • Fabric-Agent-win32-x64.zip

Linux host commands (Linux + Windows artifacts)

# From repo root
bun run electron:dist:linux

For Windows from Linux, nsis requires Wine. If Wine is unavailable, build portable output and zip win-unpacked:

# Build app artifacts (main/preload/renderer/resources)
bun run electron:build

# Try ZIP-only Windows packaging
npx electron-builder --config electron-builder.yml --project apps/electron --win zip --x64

# If Wine is missing, package win-unpacked manually:
cd apps/electron/release
rm -f Fabric-Agent-win32-x64.zip
zip -r -q Fabric-Agent-win32-x64.zip win-unpacked

Update and publish latest.json

latest.json is the source of truth for install scripts (install-app.sh / install-app.ps1).

Recommended flow:

# 1) Download current manifest (keeps existing mac entries)
npx wrangler r2 object get fabric-agents-releases/electron/latest/latest.json \
  --remote --file /tmp/latest.current.json --config cloudflare/agents-worker/wrangler.toml

# 2) Build/update linux + windows yml metadata in apps/electron/release/
#    (latest-linux.yml from linux build, latest.yml from windows zip metadata)

# 3) Write merged /tmp/latest.json (update linux-x64 + windows-x64 assets/checksums/sizes)
#    and keep darwin-* entries unless new mac builds were produced.

Upload:

npx wrangler r2 object put fabric-agents-releases/electron/latest/Fabric-Agent-x86_64.AppImage \
  --remote --file apps/electron/release/Fabric-Agent-x86_64.AppImage \
  --config cloudflare/agents-worker/wrangler.toml

npx wrangler r2 object put fabric-agents-releases/electron/latest/Fabric-Agent-win32-x64.zip \
  --remote --file apps/electron/release/Fabric-Agent-win32-x64.zip \
  --config cloudflare/agents-worker/wrangler.toml

npx wrangler r2 object put fabric-agents-releases/electron/latest/latest-linux.yml \
  --remote --file apps/electron/release/latest-linux.yml \
  --config cloudflare/agents-worker/wrangler.toml

npx wrangler r2 object put fabric-agents-releases/electron/latest/latest.yml \
  --remote --file apps/electron/release/latest.yml \
  --config cloudflare/agents-worker/wrangler.toml

npx wrangler r2 object put fabric-agents-releases/electron/latest/latest.json \
  --remote --file /tmp/latest.json \
  --config cloudflare/agents-worker/wrangler.toml

Smoke checks

  • Linux installer path: curl -fsSL https://agents.fabric.pro/install-app.sh | bash
  • Windows installer path: irm https://agents.fabric.pro/install-app.ps1 | iex

Both installers must resolve assets via https://agents.fabric.pro/electron/latest/latest.json and verify SHA512 before install.

Troubleshooting

If you don't have a Developer ID certificate, set CSC_IDENTITY_AUTO_DISCOVERY=false to skip signing:

CSC_IDENTITY_AUTO_DISCOVERY=false bash apps/electron/scripts/build-dmg.sh arm64

Unsigned builds open fine on your own machine but will be blocked by Gatekeeper on other Macs unless the user right-clicks → Open.

Notarization fails

Notarization requires:

  1. APPLE_ID — your Apple ID email
  2. APPLE_TEAM_ID — your 10-character team ID (visible in developer.apple.com)
  3. APPLE_APP_SPECIFIC_PASSWORD — an app-specific password created at appleid.apple.com

If any of these are missing, notarization is skipped and you get a signed but non-notarized DMG.

"bun: command not found" in the build script

Install Bun first:

curl -fsSL https://bun.sh/install | bash
source ~/.zshrc   # or restart your terminal

Checksum mismatch after upload

Always compute checksums after the ZIP is fully written to disk, and upload the exact file you checksummed. Re-running the build script produces a different ZIP (timestamps differ) so checksums must be recomputed if you rebuild.

Wrangler "More than one account available"

Set the account ID explicitly (ask a repo maintainer if you don't already have it — it is not stored in the repo):

export CLOUDFLARE_ACCOUNT_ID="<your-cloudflare-account-id>"

On this page