diff --git a/.icons/cmux.svg b/.icons/mux.svg similarity index 100% rename from .icons/cmux.svg rename to .icons/mux.svg diff --git a/registry/coder/modules/cmux/run.sh b/registry/coder/modules/cmux/run.sh deleted file mode 100644 index 23656f8f7..000000000 --- a/registry/coder/modules/cmux/run.sh +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env bash - -BOLD='\033[0;1m' -RESET='\033[0m' -CMUX_BINARY="${INSTALL_PREFIX}/cmux" - -function run_cmux() { - local port_value - port_value="${PORT}" - if [ -z "$port_value" ]; then - port_value="4000" - fi - echo "🚀 Starting cmux server on port $port_value..." - echo "Check logs at ${LOG_PATH}!" - PORT="$port_value" "$CMUX_BINARY" server --port "$port_value" > "${LOG_PATH}" 2>&1 & -} - -# Check if cmux is already installed for offline mode -if [ "${OFFLINE}" = true ]; then - if [ -f "$CMUX_BINARY" ]; then - echo "🥳 Found a copy of cmux" - run_cmux - exit 0 - fi - echo "❌ Failed to find a copy of cmux" - exit 1 -fi - -# If there is no cached install OR we don't want to use a cached install -if [ ! -f "$CMUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then - printf "$${BOLD}Installing cmux from npm...\n" - - # Clean up from other install (in case install prefix changed). - if [ -n "$CODER_SCRIPT_BIN_DIR" ] && [ -e "$CODER_SCRIPT_BIN_DIR/cmux" ]; then - rm "$CODER_SCRIPT_BIN_DIR/cmux" - fi - - mkdir -p "$(dirname "$CMUX_BINARY")" - - if command -v npm > /dev/null 2>&1; then - echo "📦 Installing @coder/cmux via npm into ${INSTALL_PREFIX}..." - NPM_WORKDIR="${INSTALL_PREFIX}/npm" - mkdir -p "$NPM_WORKDIR" - cd "$NPM_WORKDIR" || exit 1 - if [ ! -f package.json ]; then - echo '{}' > package.json - fi - PKG="@coder/cmux" - if [ -z "${VERSION}" ] || [ "${VERSION}" = "latest" ]; then - PKG_SPEC="$PKG@latest" - else - PKG_SPEC="$PKG@${VERSION}" - fi - if ! npm install --no-audit --no-fund --omit=dev "$PKG_SPEC"; then - echo "❌ Failed to install @coder/cmux via npm" - exit 1 - fi - # Determine the installed binary path - BIN_DIR="$NPM_WORKDIR/node_modules/.bin" - CANDIDATE="$BIN_DIR/cmux" - if [ ! -f "$CANDIDATE" ]; then - echo "❌ Could not locate cmux binary after npm install" - exit 1 - fi - chmod +x "$CANDIDATE" || true - ln -sf "$CANDIDATE" "$CMUX_BINARY" - else - echo "📥 npm not found; downloading tarball from npm registry..." - VERSION_TO_USE="${VERSION}" - if [ -z "$VERSION_TO_USE" ] || [ "$VERSION_TO_USE" = "latest" ]; then - # Try to determine the latest version - META_URL="https://registry.npmjs.org/@coder/cmux/latest" - VERSION_TO_USE="$(curl -fsSL "$META_URL" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p' | head -n1)" - if [ -z "$VERSION_TO_USE" ]; then - echo "❌ Could not determine latest version for @coder/cmux" - exit 1 - fi - fi - TARBALL_URL="https://registry.npmjs.org/@coder/cmux/-/cmux-$VERSION_TO_USE.tgz" - TMP_DIR="$(mktemp -d)" - TAR_PATH="$TMP_DIR/cmux.tgz" - if ! curl -fsSL "$TARBALL_URL" -o "$TAR_PATH"; then - echo "❌ Failed to download tarball: $TARBALL_URL" - rm -rf "$TMP_DIR" - exit 1 - fi - if ! tar -xzf "$TAR_PATH" -C "$TMP_DIR"; then - echo "❌ Failed to extract tarball" - rm -rf "$TMP_DIR" - exit 1 - fi - CANDIDATE="" - # Common locations - if [ -f "$TMP_DIR/package/bin/cmux" ]; then - CANDIDATE="$TMP_DIR/package/bin/cmux" - elif [ -f "$TMP_DIR/package/bin/cmux.js" ]; then - CANDIDATE="$TMP_DIR/package/bin/cmux.js" - elif [ -f "$TMP_DIR/package/bin/cmux.mjs" ]; then - CANDIDATE="$TMP_DIR/package/bin/cmux.mjs" - else - # Try to read package.json bin field - if [ -f "$TMP_DIR/package/package.json" ]; then - BIN_PATH=$(sed -n 's/.*"bin"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$TMP_DIR/package/package.json" | head -n1) - if [ -z "$BIN_PATH" ]; then - BIN_PATH=$(sed -n '/"bin"[[:space:]]*:[[:space:]]*{/,/}/p' "$TMP_DIR/package/package.json" | sed -n 's/.*"cmux"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1) - fi - if [ -n "$BIN_PATH" ] && [ -f "$TMP_DIR/package/$BIN_PATH" ]; then - CANDIDATE="$TMP_DIR/package/$BIN_PATH" - fi - fi - # Fallback: search for plausible filenames - if [ -z "$CANDIDATE" ] || [ ! -f "$CANDIDATE" ]; then - CANDIDATE=$(find "$TMP_DIR/package" -maxdepth 4 -type f \( -name "cmux" -o -name "cmux.js" -o -name "cmux.mjs" -o -name "cmux.cjs" \) | head -n1) - fi - fi - if [ -z "$CANDIDATE" ] || [ ! -f "$CANDIDATE" ]; then - echo "❌ Could not locate cmux binary in tarball" - rm -rf "$TMP_DIR" - exit 1 - fi - cp "$CANDIDATE" "$CMUX_BINARY" - chmod +x "$CMUX_BINARY" || true - rm -rf "$TMP_DIR" - fi - - printf "🥳 cmux has been installed in ${INSTALL_PREFIX}\n\n" -fi - -# Make cmux available in PATH if CODER_SCRIPT_BIN_DIR is set -if [ -n "$CODER_SCRIPT_BIN_DIR" ] && [ ! -e "$CODER_SCRIPT_BIN_DIR/cmux" ]; then - ln -s "$CMUX_BINARY" "$CODER_SCRIPT_BIN_DIR/cmux" -fi - -# Start cmux -run_cmux diff --git a/registry/coder/modules/cmux/README.md b/registry/coder/modules/mux/README.md similarity index 50% rename from registry/coder/modules/cmux/README.md rename to registry/coder/modules/mux/README.md index c1ced2831..9bd85e20c 100644 --- a/registry/coder/modules/cmux/README.md +++ b/registry/coder/modules/mux/README.md @@ -1,20 +1,20 @@ --- -display_name: cmux +display_name: mux description: Coding Agent Multiplexer - Run multiple AI agents in parallel -icon: ../../../../.icons/cmux.svg +icon: ../../../../.icons/mux.svg verified: false tags: [ai, agents, development, multiplexer] --- -# cmux +# mux -Automatically install and run [cmux](https://github.com/coder/cmux) in a Coder workspace. By default, the module installs `@coder/cmux@latest` from npm (with a fallback to downloading the npm tarball if npm is unavailable). cmux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated cmux workspaces. +Automatically install and run mux in a Coder workspace. By default, the module installs `mux@next` from npm (with a fallback to downloading the npm tarball if npm is unavailable). mux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated workspaces. ```tf -module "cmux" { +module "mux" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/cmux/coder" - version = "1.0.2" + source = "registry.coder.com/coder/mux/coder" + version = "1.0.0" agent_id = coder_agent.example.id } ``` @@ -22,8 +22,8 @@ module "cmux" { ## Features - **Parallel Agent Execution**: Run multiple AI agents simultaneously on different tasks -- **Cmux Workspace Isolation**: Each agent works in its own isolated environment -- **Git Divergence Visualization**: Track changes across different cmux agent workspaces +- **Mux Workspace Isolation**: Each agent works in its own isolated environment +- **Git Divergence Visualization**: Track changes across different mux agent workspaces - **Long-Running Processes**: Resume AI work after interruptions - **Cost Tracking**: Monitor API usage across agents @@ -32,10 +32,10 @@ module "cmux" { ### Basic Usage ```tf -module "cmux" { +module "mux" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/cmux/coder" - version = "1.0.2" + source = "registry.coder.com/coder/mux/coder" + version = "1.0.0" agent_id = coder_agent.example.id } ``` @@ -43,10 +43,10 @@ module "cmux" { ### Pin Version ```tf -module "cmux" { +module "mux" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/cmux/coder" - version = "1.0.2" + source = "registry.coder.com/coder/mux/coder" + version = "1.0.0" agent_id = coder_agent.example.id # Default is "latest"; set to a specific version to pin install_version = "0.4.0" @@ -56,10 +56,10 @@ module "cmux" { ### Custom Port ```tf -module "cmux" { +module "mux" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/cmux/coder" - version = "1.0.2" + source = "registry.coder.com/coder/mux/coder" + version = "1.0.0" agent_id = coder_agent.example.id port = 8080 } @@ -67,13 +67,13 @@ module "cmux" { ### Use Cached Installation -Run an existing copy of cmux if found, otherwise install from npm: +Run an existing copy of mux if found, otherwise install from npm: ```tf -module "cmux" { +module "mux" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/cmux/coder" - version = "1.0.2" + source = "registry.coder.com/coder/mux/coder" + version = "1.0.0" agent_id = coder_agent.example.id use_cached = true } @@ -81,13 +81,13 @@ module "cmux" { ### Skip Install -Run without installing from the network (requires cmux to be pre-installed): +Run without installing from the network (requires mux to be pre-installed): ```tf -module "cmux" { +module "mux" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/cmux/coder" - version = "1.0.2" + source = "registry.coder.com/coder/mux/coder" + version = "1.0.0" agent_id = coder_agent.example.id install = false } @@ -99,6 +99,6 @@ module "cmux" { ## Notes -- cmux is currently in preview and you may encounter bugs +- mux is currently in preview and you may encounter bugs - Requires internet connectivity for agent operations (unless `install` is set to false) -- Installs `@coder/cmux` from npm by default (falls back to the npm tarball if npm is unavailable) +- Installs `mux@next` from npm by default (falls back to the npm tarball if npm is unavailable) diff --git a/registry/coder/modules/cmux/main.test.ts b/registry/coder/modules/mux/main.test.ts similarity index 79% rename from registry/coder/modules/cmux/main.test.ts rename to registry/coder/modules/mux/main.test.ts index 5ff42c3f0..efc00460d 100644 --- a/registry/coder/modules/cmux/main.test.ts +++ b/registry/coder/modules/mux/main.test.ts @@ -6,7 +6,7 @@ import { testRequiredVariables, } from "~test"; -describe("cmux", async () => { +describe("mux", async () => { await runTerraformInit(import.meta.dir); testRequiredVariables(import.meta.dir, { @@ -31,9 +31,9 @@ describe("cmux", async () => { expect(output.exitCode).toBe(0); const expectedLines = [ "📥 npm not found; downloading tarball from npm registry...", - "🥳 cmux has been installed in /tmp/cmux", - "🚀 Starting cmux server on port 4000...", - "Check logs at /tmp/cmux.log!", + "🥳 mux has been installed in /tmp/mux", + "🚀 Starting mux server on port 4000...", + "Check logs at /tmp/mux.log!", ]; for (const line of expectedLines) { expect(output.stdout).toContain(line); @@ -54,10 +54,10 @@ describe("cmux", async () => { expect(output.exitCode).toBe(0); const expectedLines = [ - "📦 Installing @coder/cmux via npm into /tmp/cmux...", - "🥳 cmux has been installed in /tmp/cmux", - "🚀 Starting cmux server on port 4000...", - "Check logs at /tmp/cmux.log!", + "📦 Installing mux via npm into /tmp/mux...", + "🥳 mux has been installed in /tmp/mux", + "🚀 Starting mux server on port 4000...", + "Check logs at /tmp/mux.log!", ]; for (const line of expectedLines) { expect(output.stdout).toContain(line); diff --git a/registry/coder/modules/cmux/main.tf b/registry/coder/modules/mux/main.tf similarity index 76% rename from registry/coder/modules/cmux/main.tf rename to registry/coder/modules/mux/main.tf index 37ec52021..08c70aab6 100644 --- a/registry/coder/modules/cmux/main.tf +++ b/registry/coder/modules/mux/main.tf @@ -17,38 +17,44 @@ variable "agent_id" { variable "port" { type = number - description = "The port to run cmux on." + description = "The port to run mux on." default = 4000 } variable "display_name" { type = string - description = "The display name for the cmux application." - default = "cmux" + description = "The display name for the mux application." + default = "mux" } variable "slug" { type = string - description = "The slug for the cmux application." - default = "cmux" + description = "The slug for the mux application." + default = "mux" } variable "install_prefix" { type = string - description = "The prefix to install cmux to." - default = "/tmp/cmux" + description = "The prefix to install mux to." + default = "/tmp/mux" } variable "log_path" { type = string - description = "The path for cmux logs." - default = "/tmp/cmux.log" + description = "The path for mux logs." + default = "/tmp/mux.log" +} + +variable "add-project" { + type = string + description = "Path to add/open as a project in mux (idempotent)." + default = "" } variable "install_version" { type = string - description = "The version of cmux to install." - default = "latest" + description = "The version or dist-tag of mux to install." + default = "next" } variable "share" { @@ -74,13 +80,13 @@ variable "group" { variable "install" { type = bool - description = "Install cmux from the network (npm or tarball). If false, run without installing (requires a pre-installed cmux)." + description = "Install mux from the network (npm or tarball). If false, run without installing (requires a pre-installed mux)." default = true } variable "use_cached" { type = bool - description = "Use cached copy of cmux if present; otherwise install from npm" + description = "Use cached copy of mux if present; otherwise install from npm" default = false } @@ -107,14 +113,15 @@ variable "open_in" { } } -resource "coder_script" "cmux" { +resource "coder_script" "mux" { agent_id = var.agent_id - display_name = "cmux" - icon = "/icon/cmux.svg" + display_name = "mux" + icon = "/icon/mux.svg" script = templatefile("${path.module}/run.sh", { VERSION : var.install_version, PORT : var.port, LOG_PATH : var.log_path, + ADD_PROJECT : var.add-project, INSTALL_PREFIX : var.install_prefix, OFFLINE : !var.install, USE_CACHED : var.use_cached, @@ -129,12 +136,12 @@ resource "coder_script" "cmux" { } } -resource "coder_app" "cmux" { +resource "coder_app" "mux" { agent_id = var.agent_id slug = var.slug display_name = var.display_name url = "http://localhost:${var.port}" - icon = "/icon/cmux.svg" + icon = "/icon/mux.svg" subdomain = var.subdomain share = var.share order = var.order @@ -147,3 +154,5 @@ resource "coder_app" "cmux" { threshold = 6 } } + + diff --git a/registry/coder/modules/cmux/cmux.tftest.hcl b/registry/coder/modules/mux/mux.tftest.hcl similarity index 89% rename from registry/coder/modules/cmux/cmux.tftest.hcl rename to registry/coder/modules/mux/mux.tftest.hcl index 3b831b373..c403d377d 100644 --- a/registry/coder/modules/cmux/cmux.tftest.hcl +++ b/registry/coder/modules/mux/mux.tftest.hcl @@ -16,7 +16,7 @@ run "install_false_and_use_cached_conflict" { } expect_failures = [ - resource.coder_script.cmux + resource.coder_script.mux ] } @@ -29,7 +29,7 @@ run "custom_port" { } assert { - condition = resource.coder_app.cmux.url == "http://localhost:8080" + condition = resource.coder_app.mux.url == "http://localhost:8080" error_message = "coder_app URL must use the configured port" } } @@ -62,3 +62,5 @@ run "use_cached_only_success" { use_cached = true } } + + diff --git a/registry/coder/modules/mux/run.sh b/registry/coder/modules/mux/run.sh new file mode 100644 index 000000000..c202a9ee0 --- /dev/null +++ b/registry/coder/modules/mux/run.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash + +BOLD='\033[0;1m' +RESET='\033[0m' +MUX_BINARY="${INSTALL_PREFIX}/mux" + +function run_mux() { + local port_value + port_value="${PORT}" + if [ -z "$port_value" ]; then + port_value="4000" + fi + # Build args for mux (POSIX-compatible, avoid bash arrays) + set -- server --port "$port_value" + if [ -n "${ADD_PROJECT}" ]; then + set -- "$@" --add-project "${ADD_PROJECT}" + fi + echo "🚀 Starting mux server on port $port_value..." + echo "Check logs at ${LOG_PATH}!" + PORT="$port_value" "$MUX_BINARY" "$@" > "${LOG_PATH}" 2>&1 & +} + +# Check if mux is already installed for offline mode +if [ "${OFFLINE}" = true ]; then + if [ -f "$MUX_BINARY" ]; then + echo "🥳 Found a copy of mux" + run_mux + exit 0 + fi + echo "❌ Failed to find a copy of mux" + exit 1 +fi + +# If there is no cached install OR we don't want to use a cached install +if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then + printf "$${BOLD}Installing mux from npm...\n" + + # Clean up from other install (in case install prefix changed). + if [ -n "$CODER_SCRIPT_BIN_DIR" ] && [ -e "$CODER_SCRIPT_BIN_DIR/mux" ]; then + rm "$CODER_SCRIPT_BIN_DIR/mux" + fi + + mkdir -p "$(dirname "$MUX_BINARY")" + + if command -v npm > /dev/null 2>&1; then + echo "📦 Installing mux via npm into ${INSTALL_PREFIX}..." + NPM_WORKDIR="${INSTALL_PREFIX}/npm" + mkdir -p "$NPM_WORKDIR" + cd "$NPM_WORKDIR" || exit 1 + if [ ! -f package.json ]; then + echo '{}' > package.json + fi + PKG="mux" + if [ -z "${VERSION}" ] || [ "${VERSION}" = "latest" ]; then + PKG_SPEC="$PKG@latest" + else + PKG_SPEC="$PKG@${VERSION}" + fi + if ! npm install --no-audit --no-fund --omit=dev "$PKG_SPEC"; then + echo "❌ Failed to install mux via npm" + exit 1 + fi + # Determine the installed binary path + BIN_DIR="$NPM_WORKDIR/node_modules/.bin" + CANDIDATE="$BIN_DIR/mux" + if [ ! -f "$CANDIDATE" ]; then + echo "❌ Could not locate mux binary after npm install" + exit 1 + fi + chmod +x "$CANDIDATE" || true + ln -sf "$CANDIDATE" "$MUX_BINARY" + else + echo "📥 npm not found; downloading tarball from npm registry..." + VERSION_TO_USE="${VERSION}" + if [ -z "$VERSION_TO_USE" ]; then + VERSION_TO_USE="next" + fi + META_URL="https://registry.npmjs.org/mux/$VERSION_TO_USE" + META_JSON="$(curl -fsSL "$META_URL" || true)" + if [ -z "$META_JSON" ]; then + echo "❌ Failed to fetch npm metadata: $META_URL" + exit 1 + fi + # Normalize JSON to a single line for robust pattern matching across environments + META_ONE_LINE="$(printf "%s" "$META_JSON" | tr -d '\n' || true)" + if [ -z "$META_ONE_LINE" ]; then + META_ONE_LINE="$META_JSON" + fi + # Try to extract tarball URL directly from metadata (prefer Node if available for robust JSON parsing) + TARBALL_URL="" + if command -v node > /dev/null 2>&1; then + TARBALL_URL="$(printf "%s" "$META_JSON" | node -e 'try{const fs=require("fs");const data=JSON.parse(fs.readFileSync(0,"utf8"));if(data&&data.dist&&data.dist.tarball){console.log(data.dist.tarball);}}catch(e){}')" + fi + # sed-based fallback + if [ -z "$TARBALL_URL" ]; then + TARBALL_URL="$(printf "%s" "$META_ONE_LINE" | sed -n 's/.*\"tarball\":\"\\([^\"]*\\)\".*/\\1/p' | head -n1)" + fi + # Fallback: resolve version then construct tarball URL + if [ -z "$TARBALL_URL" ]; then + RESOLVED_VERSION="" + if command -v node > /dev/null 2>&1; then + RESOLVED_VERSION="$(printf "%s" "$META_JSON" | node -e 'try{const fs=require("fs");const data=JSON.parse(fs.readFileSync(0,"utf8"));if(data&&data.version){console.log(data.version);}}catch(e){}')" + fi + if [ -z "$RESOLVED_VERSION" ]; then + RESOLVED_VERSION="$(printf "%s" "$META_ONE_LINE" | sed -n 's/.*\"version\":\"\\([^\"]*\\)\".*/\\1/p' | head -n1)" + fi + if [ -z "$RESOLVED_VERSION" ]; then + RESOLVED_VERSION="$(printf "%s" "$META_ONE_LINE" | grep -o '\"version\":\"[^\"]*\"' | head -n1 | cut -d '\"' -f4)" + fi + if [ -n "$RESOLVED_VERSION" ]; then + VERSION_TO_USE="$RESOLVED_VERSION" + fi + if [ -z "$VERSION_TO_USE" ]; then + echo "❌ Could not determine version for mux" + exit 1 + fi + TARBALL_URL="https://registry.npmjs.org/mux/-/mux-$VERSION_TO_USE.tgz" + fi + TMP_DIR="$(mktemp -d)" + TAR_PATH="$TMP_DIR/mux.tgz" + if ! curl -fsSL "$TARBALL_URL" -o "$TAR_PATH"; then + echo "❌ Failed to download tarball: $TARBALL_URL" + rm -rf "$TMP_DIR" + exit 1 + fi + if ! tar -xzf "$TAR_PATH" -C "$TMP_DIR"; then + echo "❌ Failed to extract tarball" + rm -rf "$TMP_DIR" + exit 1 + fi + CANDIDATE="" + BIN_PATH="" + # Prefer reading bin path from package.json + if [ -f "$TMP_DIR/package/package.json" ]; then + if command -v node > /dev/null 2>&1; then + BIN_PATH="$(node -e 'try{const fs=require("fs");const p=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));let bp=typeof p.bin==="string"?p.bin:(p.bin&&p.bin.mux);if(bp){console.log(bp)}}catch(e){}' "$TMP_DIR/package/package.json")" + fi + if [ -z "$BIN_PATH" ]; then + # sed fallbacks (handle both string and object forms) + BIN_PATH=$(sed -n 's/.*\"bin\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' "$TMP_DIR/package/package.json" | head -n1) + if [ -z "$BIN_PATH" ]; then + BIN_PATH=$(sed -n '/\"bin\"[[:space:]]*:[[:space:]]*{/,/}/p' "$TMP_DIR/package/package.json" | sed -n 's/.*\"mux\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n1) + fi + fi + if [ -n "$BIN_PATH" ] && [ -f "$TMP_DIR/package/$BIN_PATH" ]; then + CANDIDATE="$TMP_DIR/package/$BIN_PATH" + fi + fi + # Fallback: check common locations + if [ -z "$CANDIDATE" ]; then + if [ -f "$TMP_DIR/package/bin/mux" ]; then + CANDIDATE="$TMP_DIR/package/bin/mux" + elif [ -f "$TMP_DIR/package/bin/mux.js" ]; then + CANDIDATE="$TMP_DIR/package/bin/mux.js" + elif [ -f "$TMP_DIR/package/bin/mux.mjs" ]; then + CANDIDATE="$TMP_DIR/package/bin/mux.mjs" + fi + fi + # Fallback: search for plausible filenames + if [ -z "$CANDIDATE" ] || [ ! -f "$CANDIDATE" ]; then + CANDIDATE=$(find "$TMP_DIR/package" -maxdepth 4 -type f \( -name "mux" -o -name "mux.js" -o -name "mux.mjs" -o -name "mux.cjs" -o -name "main.js" \) | head -n1) + fi + if [ -z "$CANDIDATE" ] || [ ! -f "$CANDIDATE" ]; then + echo "❌ Could not locate mux binary in tarball" + rm -rf "$TMP_DIR" + exit 1 + fi + # Copy entire package to installation directory to preserve relative imports + DEST_DIR="${INSTALL_PREFIX}/.mux-package" + rm -rf "$DEST_DIR" + mkdir -p "$DEST_DIR" + cp -R "$TMP_DIR/package/." "$DEST_DIR/" + # Create/refresh launcher symlink + if [ -n "$BIN_PATH" ] && [ -f "$DEST_DIR/$BIN_PATH" ]; then + ln -sf "$DEST_DIR/$BIN_PATH" "$MUX_BINARY" + chmod +x "$DEST_DIR/$BIN_PATH" || true + else + ln -sf "$DEST_DIR/$(basename "$CANDIDATE")" "$MUX_BINARY" + chmod +x "$DEST_DIR/$(basename "$CANDIDATE")" || true + fi + rm -rf "$TMP_DIR" + fi + + printf "🥳 mux has been installed in ${INSTALL_PREFIX}\n\n" +fi + +# Make mux available in PATH if CODER_SCRIPT_BIN_DIR is set +if [ -n "$CODER_SCRIPT_BIN_DIR" ]; then + if [ ! -e "$CODER_SCRIPT_BIN_DIR/mux" ]; then + ln -s "$MUX_BINARY" "$CODER_SCRIPT_BIN_DIR/mux" + fi +fi + +# Start mux +run_mux