Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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/

Expand All @@ -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
)

Expand Down Expand Up @@ -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`.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -96,7 +99,33 @@ 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.

### 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

Expand Down Expand Up @@ -189,7 +218,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
Expand Down
53 changes: 41 additions & 12 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -25,15 +26,17 @@ flowchart TD
C --> D["Create annotated tag<br/>vX.Y.Z"]
D --> E["Push tag to origin"]
E --> F["GitHub Actions: Release workflow"]
F --> G["Build platform binaries<br/>linux-x64, linux-arm64,<br/>darwin-arm64, darwin-x64,<br/>windows-x64"]
G --> H["Upload temporary Actions artifacts"]
H --> I["Release job downloads artifacts<br/>and creates or updates a<br/>draft GitHub Release"]
I --> J["Review assets and release notes"]
J --> K["Download one release asset<br/>from the draft release"]
K --> L["Verify SHA-256"]
L --> M["Run downloaded binary<br/>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<br/>linux-x64, linux-arm64,<br/>darwin-arm64, darwin-x64,<br/>windows-x64"]
F --> H["Build desktop macOS artifact<br/>DMG + raw app archive"]
G --> I["Upload temporary Actions artifacts"]
H --> I
I --> J["Release job downloads artifacts<br/>and creates or updates a<br/>draft GitHub Release"]
J --> K["Review assets and release notes"]
K --> L["Download one release asset<br/>from the draft release"]
L --> M["Verify SHA-256"]
M --> N["Run downloaded binary or app<br/>against real hardware"]
N --> O["Publish the draft release"]
O --> P["install.sh latest/download now points at this release"]
```

## Where the mechanics live
Expand All @@ -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:

Expand Down Expand Up @@ -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`

Expand Down Expand Up @@ -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`
Expand All @@ -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
Expand Down
24 changes: 23 additions & 1 deletion apps/desktop/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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.
Expand Down
11 changes: 10 additions & 1 deletion apps/desktop/electrobun.config.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -29,4 +35,7 @@ export default {
bundleCEF: false,
},
},
scripts: {
postWrap: "./scripts/post-wrap.ts",
},
} satisfies ElectrobunConfig;
9 changes: 7 additions & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
35 changes: 35 additions & 0 deletions apps/desktop/scripts/post-wrap.ts
Original file line number Diff line number Diff line change
@@ -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 = [
"<key>CFBundleVersion</key>",
` <string>${appVersion}</string>`,
" <key>CFBundleShortVersionString</key>",
` <string>${appVersion}</string>`,
].join("\n");

const updated = original.replace(
/<key>CFBundleVersion<\/key>\s*<string>.*?<\/string>/,
replacement,
);

if (updated === original) {
throw new Error(`Could not patch ${infoPlistPath} with CFBundleShortVersionString`);
}

writeFileSync(infoPlistPath, updated);
Loading
Loading