From 3ce71f83098e5c89b7bb441b699e90863c4b5341 Mon Sep 17 00:00:00 2001 From: Carl Ryden Date: Sat, 18 Apr 2026 09:27:37 -0400 Subject: [PATCH 1/2] feat: package the experimental desktop app --- .github/workflows/release.yml | 67 ++++++++++++++++++++++++++++++- .gitignore | 2 + README.md | 17 +++++--- RELEASE.md | 53 ++++++++++++++++++------ apps/desktop/README.md | 24 ++++++++++- apps/desktop/electrobun.config.ts | 11 ++++- apps/desktop/package.json | 9 ++++- apps/desktop/scripts/post-wrap.ts | 35 ++++++++++++++++ 8 files changed, 195 insertions(+), 23 deletions(-) create mode 100644 apps/desktop/scripts/post-wrap.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eded282..ff673e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -107,9 +107,63 @@ jobs: if-no-files-found: error retention-days: 30 + build-desktop-macos: + name: build desktop macos + runs-on: macos-14 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Build packaged desktop app + run: | + cd apps/desktop + bun run clean + bun run package + + - name: Normalize desktop artifact names + shell: bash + run: | + mkdir -p release-desktop + + for file in apps/desktop/artifacts/stable-macos-*-AxonServoProgrammer.dmg; do + [[ -e "$file" ]] || continue + arch="${file#apps/desktop/artifacts/stable-macos-}" + arch="${arch%-AxonServoProgrammer.dmg}" + cp "$file" "release-desktop/Axon-Servo-Programmer-macos-${arch}.dmg" + done + + for file in apps/desktop/artifacts/stable-macos-*-AxonServoProgrammer.app.tar.zst; do + [[ -e "$file" ]] || continue + arch="${file#apps/desktop/artifacts/stable-macos-}" + arch="${arch%-AxonServoProgrammer.app.tar.zst}" + cp "$file" "release-desktop/Axon-Servo-Programmer-macos-${arch}.app.tar.zst" + done + + ls -la release-desktop/ + + - name: Upload desktop artifact + uses: actions/upload-artifact@v4 + with: + name: desktop-macos + path: release-desktop/* + if-no-files-found: error + retention-days: 30 + release: name: release - needs: build + needs: + - build + - build-desktop-macos if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: @@ -126,7 +180,7 @@ jobs: - name: Flatten artifact tree run: | mkdir -p release - find artifacts -type f \( -name 'axon-*' -o -name '*.sha256' \) -exec cp {} release/ \; + find artifacts -type f \( -name 'axon-*' -o -name '*.sha256' -o -name 'Axon-Servo-Programmer-*' \) -exec cp {} release/ \; cp scripts/install.sh release/install.sh ls -la release/ @@ -144,6 +198,8 @@ jobs: axon-linux-x64.sha256 axon-windows-x64.exe axon-windows-x64.exe.sha256 + Axon-Servo-Programmer-macos-arm64.dmg + Axon-Servo-Programmer-macos-arm64.app.tar.zst install.sh ) @@ -202,3 +258,10 @@ jobs: - Full install matrix: [docs/INSTALL.md](https://github.com/caryden/servo-programmer/blob/main/docs/INSTALL.md) - Project background: [docs/the-adventure.md](https://github.com/caryden/servo-programmer/blob/main/docs/the-adventure.md) - Homebrew, npm, and Scoop remain planned follow-on channels; direct download and `install.sh` are the v1.0 path. + + ### Experimental desktop app (macOS) + + - Apple Silicon macOS build: `Axon-Servo-Programmer-macos-arm64.dmg` + - Raw app bundle archive: `Axon-Servo-Programmer-macos-arm64.app.tar.zst` + - This desktop app is still an experimental side project built with Electrobun. + - If Gatekeeper warns on first launch, open the app via Finder with right-click → `Open`. diff --git a/.gitignore b/.gitignore index e51e036..567d5e7 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,8 @@ node_modules/ *.log apps/cli/bin/ apps/cli/dist/ +apps/desktop/artifacts/ +apps/desktop/build/ apps/web/dist/ apps/web/.pages/ playwright-report/ diff --git a/README.md b/README.md index cdd781f..f2b956d 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,12 @@ > Cross-platform replacement for the legacy Axon Robotics Servo > Programming Software: a released CLI, a live browser app, and a -> desktop app built from scratch by reverse-engineering a closed HW/SW -> system with [Claude Code](https://claude.com/claude-code), a Saleae -> Logic 8, and Ghidra. +> desktop side experiment built with Electrobun to prove UI/runtime +> fidelity against the CLI and web app, and to explore a macOS app +> packaging path, all built from scratch by reverse-engineering a +> closed HW/SW system with +> [Claude Code](https://claude.com/claude-code), a Saleae Logic 8, and +> Ghidra. > > This started as a research experiment — *"how far can a single > developer get with a coding agent on a reverse-engineering project @@ -96,7 +99,11 @@ axon --version axon status ``` -There is no desktop app installer yet. The desktop app remains a source/dev build for now. +The desktop app was a side experiment with Electrobun. We pushed it far +enough to prove fidelity with the CLI/web stack and to explore a macOS +packaging path. The CLI remains the primary supported install surface; +desktop packaging is still experimental. Tagged releases are intended +to publish a macOS DMG alongside the CLI assets. ## Features @@ -189,7 +196,7 @@ The repo is now organized as a Bun workspace: - [`apps/cli/`](apps/cli/) — production `axon` CLI - [`apps/web/`](apps/web/) — browser WebHID app -- [`apps/desktop/`](apps/desktop/) — Electrobun desktop app +- [`apps/desktop/`](apps/desktop/) — Electrobun desktop PoC / side experiment - [`packages/core/`](packages/core/) — shared transport-agnostic protocol/catalog logic - [`packages/ui/`](packages/ui/) — shared probe UI used by the browser and desktop apps - [`packages/transport-nodehid/`](packages/transport-nodehid/) — Node/Bun HID transport diff --git a/RELEASE.md b/RELEASE.md index 9dd7456..a1f6cc6 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,6 +1,7 @@ # Release Process -Maintainer-facing notes for cutting and publishing a release of `axon`. +Maintainer-facing notes for cutting and publishing a release of `axon` +plus the experimental macOS desktop app artifact. `INSTALL.md` is for end users. This document is for the person shipping the binaries. @@ -25,15 +26,17 @@ flowchart TD C --> D["Create annotated tag
vX.Y.Z"] D --> E["Push tag to origin"] E --> F["GitHub Actions: Release workflow"] - F --> G["Build platform binaries
linux-x64, linux-arm64,
darwin-arm64, darwin-x64,
windows-x64"] - G --> H["Upload temporary Actions artifacts"] - H --> I["Release job downloads artifacts
and creates or updates a
draft GitHub Release"] - I --> J["Review assets and release notes"] - J --> K["Download one release asset
from the draft release"] - K --> L["Verify SHA-256"] - L --> M["Run downloaded binary
against real hardware"] - M --> N["Publish the draft release"] - N --> O["install.sh latest/download now points at this release"] + F --> G["Build CLI binaries
linux-x64, linux-arm64,
darwin-arm64, darwin-x64,
windows-x64"] + F --> H["Build desktop macOS artifact
DMG + raw app archive"] + G --> I["Upload temporary Actions artifacts"] + H --> I + I --> J["Release job downloads artifacts
and creates or updates a
draft GitHub Release"] + J --> K["Review assets and release notes"] + K --> L["Download one release asset
from the draft release"] + L --> M["Verify SHA-256"] + M --> N["Run downloaded binary or app
against real hardware"] + N --> O["Publish the draft release"] + O --> P["install.sh latest/download now points at this release"] ``` ## Where the mechanics live @@ -48,6 +51,8 @@ flowchart TD 1. Make sure `main` is the intended release commit and CI is green. 2. Update [apps/cli/package.json](apps/cli/package.json) to the release version. + The desktop app bundle version is derived from that file, so there is + no second desktop version to bump manually. 3. Update [CHANGELOG.md](CHANGELOG.md) with the dated release entry. 4. Create and push the tag: @@ -75,6 +80,7 @@ flowchart TD 7. Confirm the draft has: - all expected assets - `install.sh` + - the experimental macOS desktop DMG - release notes that match the corresponding `CHANGELOG.md` entry - the project-story link to `docs/the-adventure.md` @@ -123,13 +129,19 @@ Once published, the public release path should resolve normally under ## What the release workflow actually does -The release workflow has two jobs: +The release workflow has three jobs: 1. `build` - builds the standalone binaries on the appropriate runners - uploads them as temporary GitHub Actions artifacts -2. `release` +2. `build-desktop-macos` + - builds the experimental desktop app on `macos-14` + - packages a DMG plus a raw `.app.tar.zst` archive + - normalizes the release filenames + - uploads them as temporary GitHub Actions artifacts + +3. `release` - downloads those temporary artifacts - flattens them into one directory - adds `install.sh` @@ -143,6 +155,23 @@ Important distinction: End users only care about the release assets. +## Current release asset set + +The release currently publishes: + +- `axon-darwin-arm64` +- `axon-darwin-x64` +- `axon-linux-arm64` +- `axon-linux-x64` +- `axon-windows-x64.exe` +- matching `.sha256` files +- `install.sh` +- `Axon-Servo-Programmer-macos-arm64.dmg` +- `Axon-Servo-Programmer-macos-arm64.app.tar.zst` + +The desktop artifacts are intentionally macOS-only for now. Windows and +Linux desktop packaging are out of scope for the current release path. + ## Native HID packaging note The standalone binaries intentionally embed the platform-specific diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 40579b8..efff470 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -1,6 +1,10 @@ # Electrobun App -This is the desktop Axon Servo Programmer built with Electrobun. +This is the desktop Axon Servo Programmer built with Electrobun. It is +still best understood as a side experiment: the goal was to prove that +the shared UI and shared core logic could survive a native desktop +packaging path, not to replace the CLI or web app as the primary +surface. ## Why this shape @@ -47,6 +51,24 @@ cd /Users/caryden/github/servo-programmer/apps/desktop bun run start ``` +## Package it + +To build a production desktop artifact locally on macOS: + +```bash +cd /Users/caryden/github/servo-programmer/apps/desktop +bun run clean +bun run package +``` + +Current stable build outputs land in: + +- `apps/desktop/artifacts/stable-macos-arm64-AxonServoProgrammer.dmg` +- `apps/desktop/artifacts/stable-macos-arm64-AxonServoProgrammer.app.tar.zst` + +The DMG is the end-user install path. The `.app.tar.zst` archive is the +raw packaged bundle used as a fallback/debug artifact. + ## Notes - Keep the adapter on the host OS, not inside a VM such as Parallels. diff --git a/apps/desktop/electrobun.config.ts b/apps/desktop/electrobun.config.ts index 03b9319..a5b74a9 100644 --- a/apps/desktop/electrobun.config.ts +++ b/apps/desktop/electrobun.config.ts @@ -1,10 +1,16 @@ +import { readFileSync } from "node:fs"; import type { ElectrobunConfig } from "electrobun"; +const cliPackage = JSON.parse( + readFileSync(new URL("../cli/package.json", import.meta.url), "utf8"), +) as { version?: string }; +const appVersion = cliPackage.version ?? "0.0.1"; + export default { app: { name: "Axon Servo Programmer", identifier: "com.caryden.axon.servo-programmer", - version: "0.0.1", + version: appVersion, }, build: { views: { @@ -29,4 +35,7 @@ export default { bundleCEF: false, }, }, + scripts: { + postWrap: "./scripts/post-wrap.ts", + }, } satisfies ElectrobunConfig; diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 18771cf..ac2c2cf 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,10 +1,15 @@ { "name": "@axon/desktop-poc", - "version": "0.0.1", + "version": "1.1.0", "private": true, "type": "module", "scripts": { - "check": "bun x @biomejs/biome check src package.json electrobun.config.ts", + "check": "bun x @biomejs/biome check src scripts package.json electrobun.config.ts", + "clean": "rm -rf build artifacts", + "build": "electrobun build --env=dev", + "build:canary": "electrobun build --env=canary", + "build:stable": "electrobun build --env=stable", + "package": "electrobun build --env=stable", "start": "electrobun dev", "dev": "electrobun dev --watch", "typecheck": "tsc --noEmit" diff --git a/apps/desktop/scripts/post-wrap.ts b/apps/desktop/scripts/post-wrap.ts new file mode 100644 index 0000000..88d179c --- /dev/null +++ b/apps/desktop/scripts/post-wrap.ts @@ -0,0 +1,35 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +const wrapperBundlePath = process.env.ELECTROBUN_WRAPPER_BUNDLE_PATH; +const appVersion = process.env.ELECTROBUN_APP_VERSION; +const targetOs = process.env.ELECTROBUN_OS; + +if (!wrapperBundlePath || !appVersion || targetOs !== "macos") { + process.exit(0); +} + +const infoPlistPath = join(wrapperBundlePath, "Contents", "Info.plist"); +const original = readFileSync(infoPlistPath, "utf8"); + +if (original.includes("CFBundleShortVersionString")) { + process.exit(0); +} + +const replacement = [ + "CFBundleVersion", + ` ${appVersion}`, + " CFBundleShortVersionString", + ` ${appVersion}`, +].join("\n"); + +const updated = original.replace( + /CFBundleVersion<\/key>\s*.*?<\/string>/, + replacement, +); + +if (updated === original) { + throw new Error(`Could not patch ${infoPlistPath} with CFBundleShortVersionString`); +} + +writeFileSync(infoPlistPath, updated); From a6419d574f5e567600cc3f9d914ff83669996bdf Mon Sep 17 00:00:00 2001 From: Carl Ryden Date: Sat, 18 Apr 2026 09:44:26 -0400 Subject: [PATCH 2/2] docs: add desktop release install path --- README.md | 22 ++++++++++++++++++++++ docs/INSTALL.md | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/README.md b/README.md index f2b956d..9200853 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,28 @@ packaging path. The CLI remains the primary supported install surface; desktop packaging is still experimental. Tagged releases are intended to publish a macOS DMG alongside the CLI assets. +### Experimental desktop app (macOS) + +Download the latest experimental desktop app from the +[latest release](https://github.com/caryden/servo-programmer/releases/latest): + +- `Axon-Servo-Programmer-macos-arm64.dmg` + +Install path: + +1. download the DMG from the latest release +2. open it and drag `Axon Servo Programmer.app` into `Applications` +3. launch it from `Applications` + +Notes: + +- this app is still experimental +- current packaged desktop release is macOS Apple Silicon only +- because the app is not yet signed/notarized, macOS may require + `right-click -> Open` the first time +- desktop assets are published by the tagged release workflow; merging + to `main` alone does not create a downloadable DMG + ## Features Current source supports: diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 173ef14..66141b2 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -13,6 +13,7 @@ Pick the install method that matches how you work. | macOS (Apple Silicon or Intel)| `curl -fsSL https://github.com/caryden/servo-programmer/releases/latest/download/install.sh \| bash` | | Linux (x64 or arm64) | `curl -fsSL https://github.com/caryden/servo-programmer/releases/latest/download/install.sh \| bash` | | Windows (x64) | Download `axon-windows-x64.exe`, rename it to `axon.exe`, and put it on `%PATH%` | +| macOS desktop app (experimental) | Download `Axon-Servo-Programmer-macos-arm64.dmg` from the latest release and drag the app to `Applications` | | "I already have a Node toolchain" | `npm install -g @caryden/axon` *(once published)* | | "I already have a Bun toolchain" | `bunx @caryden/axon` *(once published; runs without installing)* | | "I'm in CI and need a one-liner" | `curl -fsSL https://github.com/caryden/servo-programmer/releases/latest/download/install.sh \| bash` | @@ -84,6 +85,30 @@ Pick the install method that matches how you work. axon --version ``` +## Experimental desktop app (macOS) + +The desktop app is an Electrobun side experiment. It shares the same +core logic and UI as the CLI/web surfaces, but the packaged app itself +is still experimental. + +Download from the [latest release](https://github.com/caryden/servo-programmer/releases/latest): + +- `Axon-Servo-Programmer-macos-arm64.dmg` + +Install: + +1. open the DMG +2. drag `Axon Servo Programmer.app` into `Applications` +3. launch it from `Applications` + +Notes: + +- current packaged desktop builds are macOS Apple Silicon only +- because the app is not yet signed/notarized, macOS may require + `right-click -> Open` on first launch +- desktop artifacts are published by the tagged release workflow; a + merge to `main` does not by itself create a downloadable DMG + ## Homebrew (macOS / Linuxbrew) Tap the formula and install: