Skip to content

Bundle tmux with packaged app #120

Closed

Description

@Juliusolsson05

Summary

Bundle a self-contained tmux binary inside packaged Agent Code so terminal-persistence (the tmux-backed terminal pane mode) works on user machines without any Homebrew install. Sibling to #119 (mitmdump bundling); both use the same third_party/ + runtimeTools pattern.

Why

tmux is currently optional but it is the only reason terminal panes survive app restarts. Requiring Homebrew for it makes setup heavier and means packaged users on stock macOS lose terminal persistence silently. The Homebrew tmux binary also dynamically links three Homebrew dylibs (libevent_core, libncursesw, libutf8proc), so we cannot just copy /opt/homebrew/bin/tmux — it will fail on a clean machine.

Decision

Build a fully static tmux at release time. Final artifact is a single ~1.5–2 MB executable with no external dependencies.

The alternative — copying the Homebrew binary plus its dylibs and rewriting install_names — was considered. Rejected: bigger (~5 MB), more moving parts, easier to break under codesigning. Documented in this issue only as a fallback if the static build hits an unsolvable snag.

Repo layout

Committed:

third_party/tmux/
  manifest.json          # tmux version + dep versions + per-arch sha256
  README.md
  LICENSE.md             # ISC
  .gitignore             # ignores cache/, build/
scripts/runtime-tools/
  build-tmux.mjs         # downloads pinned libevent/ncurses/utf8proc/tmux sources, builds static
  verify-tmux.mjs
  bump-tmux.mjs          # optional

Not committed:

third_party/tmux/cache/         # final static tmux binary per arch
third_party/tmux/build/         # transient build inputs/outputs

Manifest shape

{
  \"tool\": \"tmux\",
  \"version\": \"3.6a\",
  \"build\": \"static\",
  \"deps\": {
    \"libevent\": \"2.1.12-stable\",
    \"ncurses\": \"6.5\",
    \"utf8proc\": \"2.10.0\"
  },
  \"platforms\": {
    \"darwin-arm64\": { \"sha256\": \"...\" },
    \"darwin-x86_64\": { \"sha256\": \"...\" }
  }
}

Resolution order at runtime

  1. process.env.AGENT_CODE_TMUX
  2. Bundled static tmux copied from out/main/runtime/tmux/<os>-<arch>/tmux into userData/runtime/tmux-<ver>/tmux on first use (chmod +x on copy)
  3. Setup-cached PATH path
  4. PATH lookup
  5. Typed error with diagnostics

Setup gate

When bundled tmux exists, setup must show it as Bundled with Agent Code and stop prompting for install. Homebrew install path stays available for dev.

Packaging

  • Extend scripts/copy-packaged-resources.mjs to copy third_party/tmux/cache/<os>-<arch>/tmux into out/main/runtime/tmux/<os>-<arch>/tmux.
  • electron-builder.yml keeps out/main/runtime/**/* in asarUnpack (same line covers both tools).
  • CI release: npm run runtime:build:tmux runs on a macOS runner, caches third_party/tmux/cache/ keyed by sha256 of manifest.json.

Acceptance

  • Packaged Agent Code spawns tmux-backed terminal panes on a machine with no Homebrew and no tmux on PATH.
  • Persistence across app restarts works as today.
  • Setup gate stops asking for tmux in packaged builds.
  • tmux -V from the bundled binary returns the pinned version.
  • otool -L bundled/tmux shows no Homebrew dylib references.

Size impact

Packaged app grows by ~2 MB total (one static binary per arch). Git repo size: unchanged.

Related

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions