Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 11 additions & 11 deletions registry/coder/modules/cmux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ tags: [ai, agents, development, multiplexer]

# cmux

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" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/cmux/coder"
version = "1.0.2"
version = "1.1.0"
agent_id = coder_agent.example.id
}
```

## Features

- **Parallel Agent Execution**: Run multiple AI agents simultaneously on different tasks
- **Cmux Workspace Isolation**: Each agent works in its own isolated environment
- **Mux Workspace Isolation**: Each agent works in its own isolated environment
- **Git Divergence Visualization**: Track changes across different cmux agent workspaces
- **Long-Running Processes**: Resume AI work after interruptions
- **Cost Tracking**: Monitor API usage across agents
Expand All @@ -35,7 +35,7 @@ module "cmux" {
module "cmux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/cmux/coder"
version = "1.0.2"
version = "1.1.0"
agent_id = coder_agent.example.id
}
```
Expand All @@ -46,7 +46,7 @@ module "cmux" {
module "cmux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/cmux/coder"
version = "1.0.2"
version = "1.1.0"
agent_id = coder_agent.example.id
# Default is "latest"; set to a specific version to pin
install_version = "0.4.0"
Expand All @@ -59,35 +59,35 @@ module "cmux" {
module "cmux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/cmux/coder"
version = "1.0.2"
version = "1.1.0"
agent_id = coder_agent.example.id
port = 8080
}
```

### 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" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/cmux/coder"
version = "1.0.2"
version = "1.1.0"
agent_id = coder_agent.example.id
use_cached = true
}
```

### 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" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/cmux/coder"
version = "1.0.2"
version = "1.1.0"
agent_id = coder_agent.example.id
install = false
}
Expand All @@ -101,4 +101,4 @@ module "cmux" {

- cmux 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)
12 changes: 6 additions & 6 deletions registry/coder/modules/cmux/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
testRequiredVariables,
} from "~test";

describe("cmux", async () => {
describe("mux", async () => {
await runTerraformInit(import.meta.dir);

testRequiredVariables(import.meta.dir, {
Expand All @@ -31,8 +31,8 @@ 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...",
"🥳 mux has been installed in /tmp/cmux",
"🚀 Starting mux server on port 4000...",
"Check logs at /tmp/cmux.log!",
];
for (const line of expectedLines) {
Expand All @@ -54,9 +54,9 @@ 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...",
"📦 Installing mux via npm into /tmp/cmux...",
"🥳 mux has been installed in /tmp/cmux",
"🚀 Starting mux server on port 4000...",
"Check logs at /tmp/cmux.log!",
];
for (const line of expectedLines) {
Expand Down
24 changes: 12 additions & 12 deletions registry/coder/modules/cmux/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,38 @@ 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."
description = "The prefix to install mux to."
default = "/tmp/cmux"
}

variable "log_path" {
type = string
description = "The path for cmux logs."
description = "The path for mux logs."
default = "/tmp/cmux.log"
}

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" {
Expand All @@ -74,13 +74,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
}

Expand Down Expand Up @@ -109,7 +109,7 @@ variable "open_in" {

resource "coder_script" "cmux" {
agent_id = var.agent_id
display_name = "cmux"
display_name = "mux"
icon = "/icon/cmux.svg"
script = templatefile("${path.module}/run.sh", {
VERSION : var.install_version,
Expand Down
77 changes: 44 additions & 33 deletions registry/coder/modules/cmux/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,87 @@

BOLD='\033[0;1m'
RESET='\033[0m'
CMUX_BINARY="${INSTALL_PREFIX}/cmux"
CMUX_BINARY="${INSTALL_PREFIX}/mux"

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 "🚀 Starting mux 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"
echo "🥳 Found a copy of mux"
run_cmux
exit 0
fi
echo "❌ Failed to find a copy of cmux"
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 "$CMUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then
printf "$${BOLD}Installing cmux from npm...\n"
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/cmux" ]; then
rm "$CODER_SCRIPT_BIN_DIR/cmux"
fi
if [ -n "$CODER_SCRIPT_BIN_DIR" ] && [ -e "$CODER_SCRIPT_BIN_DIR/mux" ]; then
rm "$CODER_SCRIPT_BIN_DIR/mux"
fi

mkdir -p "$(dirname "$CMUX_BINARY")"

if command -v npm > /dev/null 2>&1; then
echo "📦 Installing @coder/cmux via npm into ${INSTALL_PREFIX}..."
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="@coder/cmux"
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 @coder/cmux via npm"
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/cmux"
CANDIDATE="$BIN_DIR/mux"
if [ ! -f "$CANDIDATE" ]; then
echo "❌ Could not locate cmux binary after npm install"
echo "❌ Could not locate mux 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
if [ -z "$VERSION_TO_USE" ]; then
VERSION_TO_USE="next"
fi
TARBALL_URL="https://registry.npmjs.org/@coder/cmux/-/cmux-$VERSION_TO_USE.tgz"
META_URL="https://registry.npmjs.org/mux/$VERSION_TO_USE"
RESOLVED_VERSION="$(curl -fsSL "$META_URL" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p' | head -n1)"
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"
TMP_DIR="$(mktemp -d)"
TAR_PATH="$TMP_DIR/cmux.tgz"
if ! curl -fsSL "$TARBALL_URL" -o "$TAR_PATH"; then
Expand All @@ -91,30 +97,30 @@ if [ ! -f "$CMUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then
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"
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"
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)
BIN_PATH=$(sed -n '/"bin"[[:space:]]*:[[:space:]]*{/,/}/p' "$TMP_DIR/package/package.json" | sed -n 's/.*"mux"[[: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)
CANDIDATE=$(find "$TMP_DIR/package" -maxdepth 4 -type f \( -name "mux" -o -name "mux.js" -o -name "mux.mjs" -o -name "mux.cjs" \) | head -n1)
fi
fi
if [ -z "$CANDIDATE" ] || [ ! -f "$CANDIDATE" ]; then
echo "❌ Could not locate cmux binary in tarball"
echo "❌ Could not locate mux binary in tarball"
rm -rf "$TMP_DIR"
exit 1
fi
Expand All @@ -123,13 +129,18 @@ if [ ! -f "$CMUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then
rm -rf "$TMP_DIR"
fi

printf "🥳 cmux has been installed in ${INSTALL_PREFIX}\n\n"
printf "🥳 mux 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"
# Make mux available in PATH if CODER_SCRIPT_BIN_DIR is set (and cmux for compatibility)
if [ -n "$CODER_SCRIPT_BIN_DIR" ]; then
if [ ! -e "$CODER_SCRIPT_BIN_DIR/mux" ]; then
ln -s "$CMUX_BINARY" "$CODER_SCRIPT_BIN_DIR/mux"
fi
if [ ! -e "$CODER_SCRIPT_BIN_DIR/cmux" ]; then
ln -s "$CMUX_BINARY" "$CODER_SCRIPT_BIN_DIR/cmux"
fi
fi

# Start cmux
# Start mux
run_cmux