open-design/tools/pack
lefarcen 4761906ed8 release: Open Design 0.5.0
- bump 13 monorepo package.json files to 0.5.0 (root + apps/{web,daemon,desktop,packaged,landing-page} + packages/{contracts,platform,sidecar,sidecar-proto} + tools/{dev,pack} + e2e); apps/packaged was already at 0.4.2 from beta lane, all others at 0.4.1
- add CHANGELOG.md [0.5.0] - 2026-05-07 entry covering 51 merged PRs since 0.4.1:
  - Added: Inspect mode (#362), accent color control + launcher (#683), connection tests for execution settings (#507), four Live Dashboard templates / skill (#778, #795, #799, #801), waitlist-page / social-media-dashboard / Orbit briefing skills (#555, #678, #671), Critique Theater Phase 5 (#524), Qoder CLI agent (#626), Nano Banana image provider (#631), HyperFrames video previews (#293), project transcript export (#493), Linux headless lifecycle (#686), Windows beta packaging + R2 publishing (#768, #805), Indonesian locale (#414), form-validation craft module (#625)
  - Changed: project file watcher ignores .venv (#531), portless Origin in CORS (#735), extended OpenAI image timeouts (#788), surfaced @nexudotio X account (#696)
  - Fixed: Copilot stdin (#727), OpenCode error frames (#700), GUI-launched agent PATH discovery (#614), Tweaks-mode tooltip (#697), chat pane overflow (#740), ws-tabs-bar scrollbar (#781), settings dialog scroll (#667), settings subtitle width (#747), design system selection persistence (#621) + test fixture (#708), PDF popup blocked alert (#664), Windows link-code-folder dialog (#698), desktop entry chrome (#655), Claude Design ZIP import on Node 24 (#591), missing Next package diagnostics (#675), README.es alignment (#611), Ukrainian template fixes (#674, #680), batch fixes (#530)
  - Documentation/Internal: OD_LEGACY_DATA_DIR migrator (#712), Linux tools-pack namespace doc (#670), pi-ai link split fix (#277), desktop e2e coverage (#306), Discord notify on resolved (#685), generated GitHub metrics + contributors wall (#718, #720)

Release workflow validation runs after merge via release-stable.
2026-05-07 20:17:15 +08:00
..
bin Add mac packaged runtime and beta release flow (#170) 2026-04-30 20:25:49 +08:00
resources Migrate beta release publishing to R2 (#805) 2026-05-07 19:13:52 +08:00
src Migrate beta release publishing to R2 (#805) 2026-05-07 19:13:52 +08:00
tests Migrate beta release publishing to R2 (#805) 2026-05-07 19:13:52 +08:00
AGENTS.md Improve Windows beta packaging and installer flow (#768) 2026-05-07 16:44:15 +08:00
esbuild.config.mjs Add mac packaged runtime and beta release flow (#170) 2026-04-30 20:25:49 +08:00
package.json release: Open Design 0.5.0 2026-05-07 20:17:15 +08:00
README.md Improve Windows beta packaging and installer flow (#768) 2026-05-07 16:44:15 +08:00
tsconfig.json Add mac packaged runtime and beta release flow (#170) 2026-04-30 20:25:49 +08:00
tsconfig.tests.json chore: enforce test directory conventions (#496) 2026-05-05 15:34:22 +08:00

tools/pack

Local packaging control plane for Open Design.

The active slice is mac-first local packaging and smoke lifecycle control:

  • tools-pack mac build --to all
  • tools-pack mac build --to app|dmg|zip
  • tools-pack mac build --to all --signed
  • tools-pack mac build --to all --portable for release artifacts that must not bake local tools-pack runtime paths
  • tools-pack mac install
  • tools-pack mac start
  • tools-pack mac stop
  • tools-pack mac logs
  • tools-pack mac uninstall
  • tools-pack mac cleanup

Build artifacts are namespace-scoped under .tmp/tools-pack/out/mac/namespaces/<namespace>/. Release artifacts keep the canonical Open Design.app bundle shape; local tools-pack install copies it as Open Design.<namespace>.app so developer namespaces can coexist without affecting runtime data/log/cache paths.

Packaged runtime state is namespace-scoped under .tmp/tools-pack/runtime/mac/namespaces/<namespace>/:

  • data/ is the daemon-managed data root passed to the daemon through the packaged sidecar launch environment.
  • logs/ contains packaged process logs for desktop, web, and daemon.
  • runtime/ is the sidecar runtime base used by the packaged desktop/web/daemon process group.
  • cache/ is reserved for namespace-local packaged cache state.
  • user-data/ is the Electron/Chromium userData root, with user-data/session/ used for sessionData.

Finder/manual launches cannot carry argv stamps on the root desktop process. To keep process fallback safe, apps/packaged writes runtime/desktop-root.json with the desktop stamp, PID, executable path, app path, and log path. tools-pack mac stop trusts that marker only when namespace/stamp/PID/command validation passes; otherwise it reports the unmanaged/not-owned reason instead of killing unknown processes.

tools-pack mac stop validation

  • If the marker is absent, stop reports not-running.
  • If the marker PID is gone, stop reports not-running and clears the stale marker.
  • If the marker PID was reused by an unrelated process, stop reports unmanaged.
  • If the marker namespace, stamp, runtime root, or command does not match the current namespace, stop reports unmanaged.

This keeps stop from killing processes outside the current namespace.

Packaged desktop also writes main-process lifecycle logs to logs/desktop/latest.log so Finder/manual launches are diagnosable. This log is intentionally scoped to packaged desktop startup/shutdown/process errors and does not capture web/renderer console output.

The packaged daemon path contract is explicit: tools-pack writes namespace/base config, apps/packaged resolves namespace paths, and the packaged sidecar launcher passes daemon managed paths via launch env. The daemon may keep its own default fallback for non-packaged launches, but packaged runtime must not rely on fallback inference from Electron userData, app bundle names, or ports.

Runtime updater integration remains a later phase.

Electron-builder resources live under tools/pack/resources/mac/. The current logo is staged there as the mac icon/DMG placeholder so future design-provided assets can replace the resource files without changing packaging code.

Local developer artifacts bake the tools-pack namespace runtime root so tools-pack mac start/stop/logs/cleanup can manage them from the repo. Release artifacts use --portable so the installed app resolves namespace data/log/runtime/user-data from the user's Electron userData root instead of the build machine's .tmp path.

Windows

Local lifecycle commands:

  • tools-pack win build --to dir for fast unpacked smoke builds.
  • tools-pack win build --to nsis for installer builds.
  • tools-pack win build --to all for both outputs.
  • tools-pack win install
  • tools-pack win start
  • tools-pack win inspect --expr "document.title"
  • tools-pack win logs
  • tools-pack win stop
  • tools-pack win cleanup
  • tools-pack win list
  • tools-pack win reset

Build artifacts are namespace-scoped under .tmp/tools-pack/out/win/namespaces/<namespace>/. Packaged runtime state is namespace-scoped under .tmp/tools-pack/runtime/win/namespaces/<namespace>/. --to dir may point built-app.json at an immutable cached win-unpacked executable while keeping namespace-local config and runtime paths outside that cache entry.

Linux

Local lifecycle commands:

  • tools-pack linux build --to all (default; produces AppImage)
  • tools-pack linux build --to appimage (explicit AppImage)
  • tools-pack linux build --to dir (unpacked output for fast iteration)
  • tools-pack linux build --containerized (run electron-builder inside electronuserland/builder:base Docker for distro-agnostic glibc compat — requires Docker)
  • tools-pack linux build --to all --portable (release artifacts that must not bake local tools-pack runtime paths)
  • tools-pack linux install
  • tools-pack linux install --headless (install the headless launcher script instead of the AppImage)
  • tools-pack linux start
  • tools-pack linux start --headless (start the headless entry — daemon + web, no Electron)
  • tools-pack linux stop
  • tools-pack linux stop --headless (stop a running headless process)
  • tools-pack linux logs
  • tools-pack linux uninstall
  • tools-pack linux cleanup

Build artifacts are namespace-scoped under .tmp/tools-pack/out/linux/namespaces/<namespace>/. Packaged runtime state is namespace-scoped under .tmp/tools-pack/runtime/linux/namespaces/<namespace>/{data,logs,runtime,cache,user-data}/. Containerized build cache lives under .tmp/tools-pack/.docker-cache/{electron,electron-builder}/.

Local installs use XDG paths:

  • AppImage: ~/.local/bin/Open-Design.<namespace>.AppImage
  • Menu entry: ~/.local/share/applications/open-design-<namespace>.desktop
  • Icon: ~/.local/share/icons/hicolor/512x512/apps/open-design-<namespace>.png

The <namespace> suffix is unconditional so multiple developer namespaces can coexist on the same desktop. The .desktop file registers the od:// scheme via MimeType=x-scheme-handler/od; and pre-sets OD_PACKAGED_NAMESPACE on the Exec= line so menu launches identify the correct namespace.

Headless mode (--headless)

Headless mode targets environments without a display (WSL2, headless servers, CI) where Electron can't run. If you have a desktop, use the AppImage; if you're SSH'd into a machine or in WSL, use headless.

--headless makes install, start, and stop operate on the headless entry (@open-design/packaged/dist/headless.mjs) instead of the AppImage. Headless mode runs daemon + web without Electron.

  • install --headless writes a shell launcher at ~/.local/bin/open-design-headless-<namespace> that bakes in the namespace and resource paths. The launcher is self-contained, but the assembled app directory at those paths must remain in place — don't move it after install.
  • start --headless spawns the headless process directly, redirects stdout/stderr to logs/desktop/latest.log, and waits up to 95s (35s for identity marker + 60s for web URL) before returning.
  • stop --headless reads the same runtime/desktop-root.json identity marker as the AppImage path, validates stamp.source === PACKAGED, sends a graceful SHUTDOWN over IPC, then terminates the process tree. It does not perform the AppImage-specific process-command check.

logs always reads logs/desktop/latest.log regardless of mode, so headless output is visible via tools-pack linux logs.

AppImage launch mode (FUSE caveat)

tools-pack linux start always spawns the AppImage with --appimage-extract-and-run. Smoke testing on Ubuntu 24.04 and Arch Linux showed that direct FUSE-mounted AppImage launches make Node module loads (Express, better-sqlite3, etc.) slow enough that the daemon sidecar consistently failed to clear apps/packaged's 35-second startup timeout. Extract-and-run unpacks the AppImage into /tmp/appimage_extracted_<hex>/ and exec's the inner Electron from there, bypassing FUSE and getting daemon boot in under 5 seconds — roughly an order-of-magnitude improvement.

Implication for end-users: if launching the installed AppImage manually (not via tools-pack linux start), pass --appimage-extract-and-run yourself, or rely on a desktop launcher / appimage-launcher daemon that handles extract-and-run automatically.

Optional system tools

tools-pack linux install and tools-pack linux uninstall invoke update-desktop-database and gtk-update-icon-cache as best-effort post-hooks. Either tool being absent (iconCache: "missing" in the output) is harmless — the icon and menu entry still work, the cache just isn't refreshed. Install via your distro:

  • Arch / CachyOS: sudo pacman -S desktop-file-utils gtk-update-icon-cache
  • Debian / Ubuntu: sudo apt install desktop-file-utils gtk-update-icon-cache
  • Fedora: sudo dnf install desktop-file-utils gtk-update-icon-cache

libfuse2 is needed for FUSE-mounted AppImage launch (the default mode when running an AppImage directly without --appimage-extract-and-run). tools-pack linux start always uses extract-and-run and bypasses FUSE entirely, so it does not need libfuse2. Most modern distros ship libfuse2 by default; older Ubuntu LTS hosts may need sudo apt install libfuse2t64 (or libfuse2 on pre-24.04).

Sandbox / chrome-sandbox

Electron 41 on Linux requires kernel.unprivileged_userns_clone=1 (default on Arch, Ubuntu 24+, Debian 12+) or AppImage's --no-sandbox fallback. Most modern distros need no extra setup.

Distro-agnostic guarantee

AppImages built natively on a rolling distro (e.g., Arch / CachyOS) link against recent glibc and may not run on stable distros (Ubuntu 22.04, Debian 12). Use --containerized to build against the wide-compat electronuserland/builder:base baseline (Ubuntu 18.04 / glibc 2.27).

Format choice: why AppImage first

Linux desktop apps in this space split across formats: VS Code ships .deb + .rpm + Snap; Discord ships AppImage + .deb; Slack ships .deb + .rpm; Cursor and Obsidian ship AppImage. We start with AppImage because it is universal (one artifact runs on any glibc-compatible distro), needs no repo plumbing, and integrates cleanly with the namespace-scoped install layout. .deb / .rpm / Snap / Flatpak can land incrementally if user demand surfaces.

Out of scope (later phases)

  • AppImage signing (--signed) — deferred pending a GPG key infrastructure decision and a user-facing verification flow design (no ETA).
  • AppImage auto-update feed (latest-linux.yml) — the linux electron-builder config has no publish block wired, so a generated feed would point users at a feed that never updates. Tracked alongside signing.
  • Additional package formats: .deb, .rpm, Snap, Flatpak.
  • Linux entry in ci.yml (release lanes only build linux; PR validation does not yet).

--to dmg is manual-install DMG output only. Any builder-generated updater metadata such as latest-mac.yml or .blockmap files is treated as scratch and cleaned from the builder directory; release-beta generates the authoritative latest-mac.yml feed during release asset preparation, pointing at the update ZIP.