Files
open-design/tools/pack
809d45f27c feat(integrations): add the local Open Design Cloud flow for Codex (#6055)
* feat(daemon): expose Vela login through CLI and MCP

* feat(mcp): add interactive local brief card

* feat(packaged): bootstrap local MCP headlessly

* feat(daemon): secure local BYOK credential profiles

* feat(cli): manage secure BYOK profiles

* feat(web): store BYOK keys in secure profiles

* fix(runs): deduplicate cloud generation requests

* feat(daemon): add end-to-end plugin observability

* fix(pack): include native PTY runtime in desktop packages

Lazy-load node-pty in the daemon so missing native binaries only
disable Terminal and interactive Keychain operations instead of
crashing startup.

Ship and validate target prebuilds in macOS and Windows packages.
Repair the macOS spawn helper before signing, and invalidate stale
Windows packaging caches.

Cover the packaged PTY path in macOS and Windows smoke tests. Require
Vela CLI for every beta desktop build target.

* fix(mcp): localize and stabilize plugin briefs

Keep collect and confirm on one MCP Apps card, report intrinsic size changes,
and remove the fragile widget-state fallback.

Localize brief forms, summaries, status, and public errors from the current
request or Host locale while preserving stable protocol ids and selections.

Keep internal runtime identifiers out of user-facing MCP guidance.

* fix(packaged): register live Studio URLs with daemon

Register the packaged web sidecar loopback origin over namespace-scoped daemon
IPC after dynamic binding.

Invalidate install-info by the live web port and remove the second MCP URL cache
so run and project responses use the current Studio address.

Validate loopback origins and cover registration, rebind, and cache refresh.

* fix(mcp): stabilize brief confirmation lifecycle

Keep confirmed briefs locked after Host publication failures and retry only the cached follow-up message instead of confirming twice.

Replace blank initialization with a compact loading state, accept immediate and delayed tool results, and bump the MCP Apps resource to v3 so Codex does not reuse the stale card.

Add an executable JSDOM Host bridge harness covering double-submit, publication retry, delayed results, and repeated draft notifications. Validated with the full daemon suite, workspace typecheck, and guard.

* fix(mcp): prevent brief app resize feedback loops

Replace the self-observed intrinsic-height loop with deduplicated body measurements so Codex MCP app iframes do not repeatedly resize themselves and fail sandbox startup.

Bump the brief resource URI to v4 to avoid cached v3 cards and cover the regression with the focused brief-app spec. Validated with daemon tests/build, workspace guard/typecheck, a verified beta DMG, and a direct packaged MCP resource smoke.

* fix(mcp): use the final Open Design plugin identity

Make open-design the only accepted external plugin ID across MCP tools, run attribution, artifact version origins, and telemetry. Rename the Brief MCP App resource to v5 and reject the unpublished old ID instead of aliasing it.

Validated with 171 daemon tests, 5 web tests, daemon/web typechecks, and guard.

* chore(pack): bump Vela CLI to 0.0.27

* chore(nix): refresh pnpm dependency hashes

* fix(mcp): honor host and plugin request contracts

Respect independently advertised MCP Apps capabilities and prefer the Codex-native follow-up bridge so confirmed briefs render as readable user turns instead of context-only messages. Bump the brief resource to v6 to invalidate stale cards.\n\nMark Vela login and account probes as open-world interactions, and require stable request IDs for attributed generation so lost responses cannot create a second logical run.

* fix(cli): support keyless BYOK profiles

Add an explicit --no-api-key save mode for local and self-hosted providers while keeping credential-bearing profiles stdin-only. Reject missing or conflicting credential modes and cover both paths in the CLI tests.

* fix(pack): replace headless owner when desktop opens

Advertise headless packaged runtimes as having no visible window, then shut them down before a normal desktop launch continues instead of treating SHOW as a successful focus.

Validated with the focused packaged headless and launcher lifecycle tests (20 passed).

* fix(pack): clean up failed headless startup

Acquire the headless identity, sidecars, and IPC server through one failure-atomic lifecycle so later bootstrap errors close every previously acquired owner in reverse order.

Cover MCP-install and web-identity publication failures; validated with 22 focused tests and the packaged typecheck.

* fix(byok): remove raw provider request contract

Expose only daemon-owned BYOK profile references from shared run types and the web transport, while retaining the daemon's fail-closed rejection for legacy or untyped raw credential payloads.

Validated with 76 web provider tests plus web and contracts typechecks.

* fix(daemon): preserve interrupted run idempotency

Hydrate queued or running durable requests as DAEMON_RESTARTED failures before idempotency reuse, preserving the original request and workflow mappings instead of creating a second cloud operation.

Share the restart transition with boot reconciliation and expose synthetic terminal events to reattaching clients. Validated with 48 focused tests and the daemon typecheck.

* fix(byok): roll back failed credential metadata writes

Snapshot secure-store values before mutation and compensate create, update, and delete operations when profile metadata cannot be persisted, preventing orphaned or lost credentials.

Validated with nine focused credential-service tests and the daemon typecheck.

* fix(web): reconcile selected BYOK profile metadata

Treat the daemon's selected secure profile as authoritative for protocol, endpoint, model, and API version so CLI profile edits cannot leave the browser showing stale execution settings.

Validated with 66 config tests and the web typecheck.

* fix(byok): migrate legacy credentials before cleanup

Import each supported legacy browser credential into a stable daemon-owned secure profile before removing plaintext copies. Keep the original browser record on secure-store or unsupported-provider failures and surface a Settings recovery path.

Cover successful migration, duplicate projections, and injected secure-backend failure while preserving existing Bedrock downgrade behavior in memory.

* fix(byok): secure Windows profiles with DPAPI

Dispatch native Windows BYOK profiles to a CurrentUser DPAPI backend rooted under the resolved daemon data directory. Secrets enter PowerShell only through stdin, encrypted blobs stay separate from non-secret profile metadata, and API responses remain secret-free.

Add platform dispatch coverage, a Windows-only real DPAPI round-trip test, and the packaged Windows smoke path for create, resolve, response-redaction, and delete behavior.

* fix(web): preserve config writes during hydration

* fix(byok): persist secure credentials across entry flows

* test(byok): cover secure onboarding persistence

---------

Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-07-30 02:00:56 +00:00
..

tools/pack

Local packaging control plane for Open Design.

tools-pack is the cross-platform packaging and smoke-lifecycle control plane. The macOS commands include:

  • 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>/. Public release bundles keep channel-distinct identities: Open Design.app, Open Design Beta.app, Open Design Prerelease.app, or Open Design Preview.app. Local tools-pack install adds the developer namespace so installs can coexist without affecting runtime data/log/cache paths.

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

  • Packaged daemon storage is governed only by the root AGENTS.md section Daemon data directory contract. Before changing or documenting packaged storage propagation, you MUST read that section; this README MUST NOT restate it.
  • 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 lives only in the root AGENTS.md section Daemon data directory contract. Before changing or documenting packaged path propagation, you MUST read that section; this README MUST NOT restate it.

Packaged desktop checks release metadata, verifies the downloaded artifact, and exposes update actions through desktop IPC. Launcher-based builds prefer verified payload activation followed by relaunch; installer replacement remains the fallback for artifact types and older builds that cannot apply a payload in place.

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.

macOS compatibility notes

  • tools-pack mac build --portable --to zip is the safest manual-install artifact for Intel Macs. This path was smoke-tested on macOS 12.7.6 Monterey on a 2015 Intel iMac and the app launched successfully from /Applications.
  • Finder/manual launches on macOS may not inherit your shell-managed PATH. If packaged Open Design cannot detect agent CLIs that work in Terminal, expose those binaries to the GUI login environment or launch the packaged app from a shell session that already sees them.

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 a wider glibc compatibility target — 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 inspect (desktop status, eval, and screenshot for AppImage mode)
  • tools-pack linux inspect --headless (status only)
  • tools-pack linux logs
  • tools-pack linux uninstall
  • tools-pack linux uninstall --headless
  • tools-pack linux cleanup
  • tools-pack linux cleanup --headless

Build artifacts are namespace-scoped under .tmp/tools-pack/out/linux/namespaces/<namespace>/. Packaged logs, sidecar runtime, cache, and Electron user-data are namespace-scoped under the tools-pack runtime root. Daemon storage follows only the root AGENTS.md Daemon data directory contract. 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, stop, uninstall, and cleanup 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 runtime/headless-root.json (separate from the AppImage path's runtime/desktop-root.json), validates the marker's packaged desktop stamp and namespace root, sends a graceful SHUTDOWN over IPC, then terminates the process tree. The separate marker prevents headless stop/cleanup from claiming a menu-launched AppImage; unlike AppImage stop, it does not perform the AppImage-specific process-command check.
  • inspect --headless returns status only. Eval and screenshot require AppImage mode because there is no Electron renderer in headless mode.
  • uninstall --headless removes the headless launcher after a safe stop.
  • cleanup --headless stops the headless process before removing namespace output/runtime roots.

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 compatibility target

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 electronuserland/builder:base baseline (Ubuntu 18.04 / glibc 2.27), which is the compatibility target for release AppImages rather than a guarantee for every Linux distribution.

Verified smoke coverage in this repository currently includes:

  • Main PR CI: packaged Linux smoke is intentionally outside the main CI gate.
  • Release lane: Ubuntu GitHub-hosted runner, containerized AppImage build plus Xvfb AppImage runtime smoke when the Linux release lane is enabled.
  • Manual AppImage behavior used to choose --appimage-extract-and-run: Ubuntu 24.04 and Arch Linux.

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 one artifact can cover the widest glibc-compatible target without distro repositories, store packaging, signing infrastructure, or per-format install scripts, and it integrates cleanly with the namespace-scoped install layout. .deb / .rpm / Snap / Flatpak can land incrementally when user demand justifies the extra release ownership.

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 — deferred until there is demand and an owner for per-distro metadata, signing/store/repository plumbing, install/remove hooks, and release validation.
  • Full Linux AppImage and headless packaged smoke remain outside the main PR gate; run the applicable tools-pack validation manually or through a release lane when Linux packaging changes.

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