Release v1.23.0 (#31)

* Rename cmuxterm to cmux across entire codebase

- Rename GitHub repos: manaflow-ai/cmuxterm -> manaflow-ai/cmux,
  manaflow-ai/homebrew-cmuxterm -> manaflow-ai/homebrew-cmux
- Rename bundle IDs: com.cmuxterm.app -> com.cmux.app
- Rename CLI: CLI/cmuxterm.swift -> CLI/cmux.swift
- Rename homebrew submodule: homebrew-cmuxterm -> homebrew-cmux
- Update all socket paths: /tmp/cmuxterm*.sock -> /tmp/cmux*.sock
- Update all GitHub URLs, DMG names, Sparkle URLs
- Update all source files, scripts, tests, docs, CI workflows

* Bump version to 1.23.0
This commit is contained in:
Lawrence Chen 2026-02-09 15:30:43 -08:00 committed by GitHub
parent 9ba1d02b2b
commit 9817d131f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 466 additions and 450 deletions

View file

@ -1,6 +1,6 @@
# Release
Prepare a new release for cmuxterm. This command updates the changelog, bumps the version, creates a PR, monitors CI, and then merges and tags.
Prepare a new release for cmux. This command updates the changelog, bumps the version, creates a PR, monitors CI, and then merges and tags.
## Steps
@ -51,8 +51,8 @@ Prepare a new release for cmuxterm. This command updates the changelog, bumps th
- Push tag: `git push origin vX.Y.Z`
11. **Monitor the release workflow**
- Watch: `gh run watch --repo manaflow-ai/cmuxterm`
- Verify the release appears at: https://github.com/manaflow-ai/cmuxterm/releases
- Watch: `gh run watch --repo manaflow-ai/cmux`
- Verify the release appears at: https://github.com/manaflow-ai/cmux/releases
- Check that the DMG is attached to the release
## Changelog Guidelines

View file

@ -85,11 +85,11 @@ jobs:
- name: Inject Sparkle keys into Info.plist
run: |
APP_PLIST="build/Build/Products/Release/cmuxterm.app/Contents/Info.plist"
APP_PLIST="build/Build/Products/Release/cmux.app/Contents/Info.plist"
echo "Adding SUPublicEDKey to Info.plist..."
/usr/libexec/PlistBuddy -c "Add :SUPublicEDKey string ${SPARKLE_PUBLIC_KEY}" "$APP_PLIST"
echo "Adding SUFeedURL to Info.plist..."
/usr/libexec/PlistBuddy -c "Add :SUFeedURL string https://github.com/manaflow-ai/cmuxterm/releases/latest/download/appcast.xml" "$APP_PLIST"
/usr/libexec/PlistBuddy -c "Add :SUFeedURL string https://github.com/manaflow-ai/cmux/releases/latest/download/appcast.xml" "$APP_PLIST"
echo "Verifying:"
/usr/libexec/PlistBuddy -c "Print :SUPublicEDKey" "$APP_PLIST"
/usr/libexec/PlistBuddy -c "Print :SUFeedURL" "$APP_PLIST"
@ -125,8 +125,8 @@ jobs:
echo "Missing APPLE_SIGNING_IDENTITY secret" >&2
exit 1
fi
APP_PATH="build/Build/Products/Release/cmuxterm.app"
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmuxterm"
APP_PATH="build/Build/Products/Release/cmux.app"
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
if [ -f "$CLI_PATH" ]; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" "$CLI_PATH"
fi
@ -144,9 +144,9 @@ jobs:
echo "Missing notarization secrets (APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID)" >&2
exit 1
fi
APP_PATH="build/Build/Products/Release/cmuxterm.app"
ZIP_SUBMIT="cmuxterm-notary.zip"
DMG_RELEASE="cmuxterm-macos.dmg"
APP_PATH="build/Build/Products/Release/cmux.app"
ZIP_SUBMIT="cmux-notary.zip"
DMG_RELEASE="cmux-macos.dmg"
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ZIP_SUBMIT"
APP_SUBMIT_JSON="$(xcrun notarytool submit "$ZIP_SUBMIT" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)"
APP_SUBMIT_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$APP_SUBMIT_JSON")"
@ -165,7 +165,7 @@ jobs:
--identity="$APPLE_SIGNING_IDENTITY" \
"$APP_PATH" \
./
mv ./cmuxterm*.dmg "$DMG_RELEASE"
mv ./cmux*.dmg "$DMG_RELEASE"
DMG_SUBMIT_JSON="$(xcrun notarytool submit "$DMG_RELEASE" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)"
DMG_SUBMIT_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$DMG_SUBMIT_JSON")"
DMG_STATUS="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' <<<"$DMG_SUBMIT_JSON")"
@ -185,13 +185,13 @@ jobs:
echo "Missing SPARKLE_PRIVATE_KEY secret" >&2
exit 1
fi
./scripts/sparkle_generate_appcast.sh cmuxterm-macos.dmg "$GITHUB_REF_NAME" appcast.xml
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "$GITHUB_REF_NAME" appcast.xml
- name: Upload release asset
uses: softprops/action-gh-release@v2
with:
files: |
cmuxterm-macos.dmg
cmux-macos.dmg
appcast.xml
generate_release_notes: true

View file

@ -31,31 +31,31 @@ jobs:
id: sha
run: |
VERSION="${{ steps.version.outputs.version }}"
curl -sL "https://github.com/manaflow-ai/cmuxterm/releases/download/v${VERSION}/cmuxterm-macos.dmg" -o cmuxterm.dmg
SHA256=$(shasum -a 256 cmuxterm.dmg | cut -d' ' -f1)
curl -sL "https://github.com/manaflow-ai/cmux/releases/download/v${VERSION}/cmux-macos.dmg" -o cmux.dmg
SHA256=$(shasum -a 256 cmux.dmg | cut -d' ' -f1)
echo "sha256=$SHA256" >> $GITHUB_OUTPUT
- name: Checkout homebrew-cmuxterm
- name: Checkout homebrew-cmux
uses: actions/checkout@v4
with:
repository: manaflow-ai/homebrew-cmuxterm
repository: manaflow-ai/homebrew-cmux
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: homebrew-cmuxterm
path: homebrew-cmux
- name: Update cask formula
env:
VERSION: ${{ steps.version.outputs.version }}
SHA256: ${{ steps.sha.outputs.sha256 }}
run: |
cat > homebrew-cmuxterm/Casks/cmuxterm.rb << CASKEOF
cask "cmuxterm" do
cat > homebrew-cmux/Casks/cmux.rb << CASKEOF
cask "cmux" do
version "${VERSION}"
sha256 "${SHA256}"
url "https://github.com/manaflow-ai/cmuxterm/releases/download/v#{version}/cmuxterm-macos.dmg"
name "cmuxterm"
url "https://github.com/manaflow-ai/cmux/releases/download/v#{version}/cmux-macos.dmg"
name "cmux"
desc "Lightweight native macOS terminal with vertical tabs for AI coding agents"
homepage "https://github.com/manaflow-ai/cmuxterm"
homepage "https://github.com/manaflow-ai/cmux"
livecheck do
url :url
@ -64,29 +64,29 @@ jobs:
depends_on macos: ">= :ventura"
app "cmuxterm.app"
app "cmux.app"
zap trash: [
"~/Library/Application Support/cmuxterm",
"~/Library/Caches/cmuxterm",
"~/Library/Preferences/ai.manaflow.cmuxterm.plist",
"~/Library/Application Support/cmux",
"~/Library/Caches/cmux",
"~/Library/Preferences/ai.manaflow.cmux.plist",
]
end
CASKEOF
# Remove leading whitespace from heredoc
sed -i 's/^ //' homebrew-cmuxterm/Casks/cmuxterm.rb
sed -i 's/^ //' homebrew-cmux/Casks/cmux.rb
- name: Commit and push
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
cd homebrew-cmuxterm
cd homebrew-cmux
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Casks/cmuxterm.rb
git add Casks/cmux.rb
if git diff --staged --quiet; then
echo "No changes - cask already up to date"
else
git commit -m "Update cmuxterm to ${VERSION}"
git commit -m "Update cmux to ${VERSION}"
git push
fi

6
.gitmodules vendored
View file

@ -2,6 +2,6 @@
path = ghostty
url = https://github.com/manaflow-ai/ghostty.git
branch = main
[submodule "homebrew-cmuxterm"]
path = homebrew-cmuxterm
url = https://github.com/manaflow-ai/homebrew-cmuxterm.git
[submodule "homebrew-cmux"]
path = homebrew-cmux
url = https://github.com/manaflow-ai/homebrew-cmux.git

View file

@ -1,6 +1,15 @@
# Changelog
All notable changes to cmuxterm are documented here.
All notable changes to cmux are documented here.
## [1.23.0] - 2026-02-09
### Changed
- Rename app from cmuxterm to cmux — new app name, bundle ID, socket paths, Homebrew tap, and CLI binary name
- Sidebar now shows tab status as text instead of colored dots, with instant git HEAD change detection
### Fixed
- CLI `set-status` command not properly quoting values or routing `--tab` flag
## [1.22.0] - 2026-02-09
@ -114,7 +123,7 @@ All notable changes to cmuxterm are documented here.
### Added
- Sentry SDK for crash reporting
- Documentation site with Fumadocs
- Homebrew installation support (`brew install --cask cmuxterm`)
- Homebrew installation support (`brew install --cask cmux`)
- Auto-update Homebrew cask on release
### Fixed
@ -160,7 +169,7 @@ All notable changes to cmuxterm are documented here.
## [1.4.0] - 2025-01-28
### Added
- cmuxterm CLI with socket control modes
- cmux CLI with socket control modes
- NSPopover-based notifications
### Fixed
@ -184,7 +193,7 @@ All notable changes to cmuxterm are documented here.
- Update UI error details and pill visibility
### Changed
- Renamed app to cmuxterm
- Renamed app to cmux
- Improved CI UI test stability
## [1.1.0] - 2025-01-28

View file

@ -1,4 +1,4 @@
# cmuxterm agent notes
# cmux agent notes
## Initial setup
@ -83,7 +83,7 @@ ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && xcodebuild -project GhosttyTabs.xcode
Run basic automated tests on the UTM macOS VM (never on the host machine):
```bash
ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination "platform=macOS" build && pkill -x "cmuxterm DEV" || true && APP=$(find /Users/cmux/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmuxterm DEV.app" -print -quit) && open "$APP" && for i in {1..20}; do [ -S /tmp/cmuxterm.sock ] && break; sleep 0.5; done && python3 tests/test_update_timing.py && python3 tests/test_signals_auto.py && python3 tests/test_ctrl_socket.py && python3 tests/test_notifications.py'
ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination "platform=macOS" build && pkill -x "cmux DEV" || true && APP=$(find /Users/cmux/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmux DEV.app" -print -quit) && open "$APP" && for i in {1..20}; do [ -S /tmp/cmux.sock ] && break; sleep 0.5; done && python3 tests/test_update_timing.py && python3 tests/test_signals_auto.py && python3 tests/test_ctrl_socket.py && python3 tests/test_notifications.py'
```
## Ghostty submodule workflow
@ -143,13 +143,13 @@ Manual release steps (if not using the command):
```bash
git tag vX.Y.Z
git push origin vX.Y.Z
gh run watch --repo manaflow-ai/cmuxterm
gh run watch --repo manaflow-ai/cmux
```
Notes:
- Requires GitHub secrets: `APPLE_CERTIFICATE_BASE64`, `APPLE_CERTIFICATE_PASSWORD`,
`APPLE_SIGNING_IDENTITY`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID`.
- The release asset is `cmuxterm-macos.dmg` attached to the tag.
- README download button points to `releases/latest/download/cmuxterm-macos.dmg`.
- The release asset is `cmux-macos.dmg` attached to the tag.
- README download button points to `releases/latest/download/cmux-macos.dmg`.
- Versioning: bump the minor version for updates unless explicitly asked otherwise.
- Changelog: always update both `CHANGELOG.md` and the docs-site version.

View file

@ -129,7 +129,7 @@ struct CMUXCLI {
let args: [String]
func run() throws {
var socketPath = ProcessInfo.processInfo.environment["CMUX_SOCKET_PATH"] ?? "/tmp/cmuxterm.sock"
var socketPath = ProcessInfo.processInfo.environment["CMUX_SOCKET_PATH"] ?? "/tmp/cmux.sock"
var jsonOutput = false
var index = 1
@ -640,10 +640,10 @@ struct CMUXCLI {
private func usage() -> String {
return """
cmuxterm - control cmuxterm via Unix socket
cmux - control cmux via Unix socket
Usage:
cmuxterm [--socket PATH] [--json] <command> [options]
cmux [--socket PATH] [--json] <command> [options]
Commands:
ping

View file

@ -1,4 +1,4 @@
# Contributing to cmuxterm
# Contributing to cmux
## Prerequisites
@ -10,8 +10,8 @@
1. Clone the repository with submodules:
```bash
git clone --recursive https://github.com/manaflow-ai/cmuxterm.git
cd cmuxterm
git clone --recursive https://github.com/manaflow-ai/cmux.git
cd cmux
```
2. Run the setup script:
@ -20,7 +20,7 @@
```
This will:
- Initialize git submodules (ghostty, homebrew-cmuxterm)
- Initialize git submodules (ghostty, homebrew-cmux)
- Build the GhosttyKit.xcframework from source
- Create the necessary symlinks
@ -53,7 +53,7 @@ zig build -Demit-xcframework=true -Doptimize=ReleaseFast
### Basic tests (run on VM)
```bash
ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination "platform=macOS" build && pkill -x "cmuxterm DEV" || true && APP=$(find /Users/cmux/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmuxterm DEV.app" -print -quit) && open "$APP" && for i in {1..20}; do [ -S /tmp/cmuxterm.sock ] && break; sleep 0.5; done && python3 tests/test_update_timing.py && python3 tests/test_signals_auto.py && python3 tests/test_ctrl_socket.py && python3 tests/test_notifications.py'
ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination "platform=macOS" build && pkill -x "cmux DEV" || true && APP=$(find /Users/cmux/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmux DEV.app" -print -quit) && open "$APP" && for i in {1..20}; do [ -S /tmp/cmux.sock ] && break; sleep 0.5; done && python3 tests/test_update_timing.py && python3 tests/test_signals_auto.py && python3 tests/test_ctrl_socket.py && python3 tests/test_notifications.py'
```
### UI tests (run on VM)

View file

@ -42,8 +42,8 @@
A5001240 /* WindowDecorationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001241 /* WindowDecorationsController.swift */; };
A5001100 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5001101 /* Assets.xcassets */; };
A5001230 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A5001231 /* Sparkle */; };
B9000002A1B2C3D4E5F60719 /* cmuxterm.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000001A1B2C3D4E5F60719 /* cmuxterm.swift */; };
B900000BA1B2C3D4E5F60719 /* cmuxterm in Copy CLI */ = {isa = PBXBuildFile; fileRef = B9000004A1B2C3D4E5F60719 /* cmuxterm */; };
B9000002A1B2C3D4E5F60719 /* cmux.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000001A1B2C3D4E5F60719 /* cmux.swift */; };
B900000BA1B2C3D4E5F60719 /* cmux in Copy CLI */ = {isa = PBXBuildFile; fileRef = B9000004A1B2C3D4E5F60719 /* cmux */; };
84E00D47E4584162AE53BC8D /* xterm-ghostty in Resources */ = {isa = PBXBuildFile; fileRef = B2E7294509CC42FE9191870E /* xterm-ghostty */; };
C1D2E3F4A5B6C7D8E9F00001 /* shell-integration in Resources */ = {isa = PBXBuildFile; fileRef = C1D2E3F4A5B6C7D8E9F00002 /* shell-integration */; };
B9000012A1B2C3D4E5F60719 /* AutomationSocketUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000011A1B2C3D4E5F60719 /* AutomationSocketUITests.swift */; };
@ -69,7 +69,7 @@
dstPath = "bin";
dstSubfolderSpec = 7;
files = (
B900000BA1B2C3D4E5F60719 /* cmuxterm in Copy CLI */,
B900000BA1B2C3D4E5F60719 /* cmux in Copy CLI */,
);
name = "Copy CLI";
runOnlyForDeploymentPostprocessing = 0;
@ -88,13 +88,13 @@
isa = PBXContainerItemProxy;
containerPortal = A5001070 /* Project object */;
proxyType = 1;
remoteGlobalIDString = B9000005A1B2C3D4E5F60719 /* cmuxterm-cli */;
remoteInfo = "cmuxterm-cli";
remoteGlobalIDString = B9000005A1B2C3D4E5F60719 /* cmux-cli */;
remoteInfo = "cmux-cli";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
A5001000 /* cmuxterm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cmuxterm.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5001000 /* cmux.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cmux.app; sourceTree = BUILT_PRODUCTS_DIR; };
7E7E6EF344A568AC7FEE3715 /* GhosttyTabsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GhosttyTabsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
A5001011 /* cmuxApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cmuxApp.swift; sourceTree = "<group>"; };
A5001012 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@ -135,8 +135,8 @@
A5001101 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B2E7294509CC42FE9191870E /* xterm-ghostty */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty/terminfo/78/xterm-ghostty"; sourceTree = "<group>"; };
C1D2E3F4A5B6C7D8E9F00002 /* shell-integration */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "shell-integration"; sourceTree = "<group>"; };
B9000001A1B2C3D4E5F60719 /* cmuxterm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cmuxterm.swift; sourceTree = "<group>"; };
B9000004A1B2C3D4E5F60719 /* cmuxterm */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cmuxterm; sourceTree = BUILT_PRODUCTS_DIR; };
B9000001A1B2C3D4E5F60719 /* cmux.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cmux.swift; sourceTree = "<group>"; };
B9000004A1B2C3D4E5F60719 /* cmux */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cmux; sourceTree = BUILT_PRODUCTS_DIR; };
B9000011A1B2C3D4E5F60719 /* AutomationSocketUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationSocketUITests.swift; sourceTree = "<group>"; };
B9000013A1B2C3D4E5F60719 /* JumpToUnreadUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpToUnreadUITests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -204,7 +204,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -euo pipefail\nDEST=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\nGHOSTTY_DEST=\"${DEST}/ghostty\"\nTERMINFO_DEST=\"${DEST}/terminfo\"\nSRC_SHARE=\"${SRCROOT}/ghostty/zig-out/share\"\nGHOSTTY_SRC=\"${SRC_SHARE}/ghostty\"\nTERMINFO_SRC=\"${SRC_SHARE}/terminfo\"\nFALLBACK_GHOSTTY=\"${SRCROOT}/Resources/ghostty\"\nFALLBACK_TERMINFO=\"${SRCROOT}/Resources/ghostty/terminfo\"\nTERMINFO_OVERLAY=\"${SRCROOT}/Resources/terminfo-overlay\"\nif [ -d \"$GHOSTTY_SRC\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$GHOSTTY_SRC/\" \"$GHOSTTY_DEST/\"\nelif [ -d \"$FALLBACK_GHOSTTY\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$FALLBACK_GHOSTTY/\" \"$GHOSTTY_DEST/\"\nfi\nif [ -d \"$TERMINFO_SRC\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$TERMINFO_SRC/\" \"$TERMINFO_DEST/\"\nelif [ -d \"$FALLBACK_TERMINFO\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$FALLBACK_TERMINFO/\" \"$TERMINFO_DEST/\"\nfi\n# Overlay any cmuxterm-specific terminfo adjustments.\n# This intentionally does not use --delete so we only patch specific entries.\nif [ -d \"$TERMINFO_OVERLAY\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a \"$TERMINFO_OVERLAY/\" \"$TERMINFO_DEST/\"\nfi\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nCOMMIT=\"$(git -C \"${SRCROOT}\" rev-parse --short=9 HEAD 2>/dev/null || true)\"\nif [ -n \"$COMMIT\" ] && [ -f \"$INFO_PLIST\" ]; then\n /usr/libexec/PlistBuddy -c \"Set :CMUXCommit $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || /usr/libexec/PlistBuddy -c \"Add :CMUXCommit string $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || true\nfi\n";
shellScript = "set -euo pipefail\nDEST=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\nGHOSTTY_DEST=\"${DEST}/ghostty\"\nTERMINFO_DEST=\"${DEST}/terminfo\"\nSRC_SHARE=\"${SRCROOT}/ghostty/zig-out/share\"\nGHOSTTY_SRC=\"${SRC_SHARE}/ghostty\"\nTERMINFO_SRC=\"${SRC_SHARE}/terminfo\"\nFALLBACK_GHOSTTY=\"${SRCROOT}/Resources/ghostty\"\nFALLBACK_TERMINFO=\"${SRCROOT}/Resources/ghostty/terminfo\"\nTERMINFO_OVERLAY=\"${SRCROOT}/Resources/terminfo-overlay\"\nif [ -d \"$GHOSTTY_SRC\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$GHOSTTY_SRC/\" \"$GHOSTTY_DEST/\"\nelif [ -d \"$FALLBACK_GHOSTTY\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$FALLBACK_GHOSTTY/\" \"$GHOSTTY_DEST/\"\nfi\nif [ -d \"$TERMINFO_SRC\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$TERMINFO_SRC/\" \"$TERMINFO_DEST/\"\nelif [ -d \"$FALLBACK_TERMINFO\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$FALLBACK_TERMINFO/\" \"$TERMINFO_DEST/\"\nfi\n# Overlay any cmux-specific terminfo adjustments.\n# This intentionally does not use --delete so we only patch specific entries.\nif [ -d \"$TERMINFO_OVERLAY\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a \"$TERMINFO_OVERLAY/\" \"$TERMINFO_DEST/\"\nfi\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nCOMMIT=\"$(git -C \"${SRCROOT}\" rev-parse --short=9 HEAD 2>/dev/null || true)\"\nif [ -n \"$COMMIT\" ] && [ -f \"$INFO_PLIST\" ]; then\n /usr/libexec/PlistBuddy -c \"Set :CMUXCommit $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || /usr/libexec/PlistBuddy -c \"Add :CMUXCommit string $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || true\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
@ -265,7 +265,7 @@
B9000003A1B2C3D4E5F60719 /* CLI */ = {
isa = PBXGroup;
children = (
B9000001A1B2C3D4E5F60719 /* cmuxterm.swift */,
B9000001A1B2C3D4E5F60719 /* cmux.swift */,
);
path = CLI;
sourceTree = "<group>";
@ -282,8 +282,8 @@
A5001042 /* Products */ = {
isa = PBXGroup;
children = (
A5001000 /* cmuxterm.app */,
B9000004A1B2C3D4E5F60719 /* cmuxterm */,
A5001000 /* cmux.app */,
B9000004A1B2C3D4E5F60719 /* cmux */,
7E7E6EF344A568AC7FEE3715 /* GhosttyTabsUITests.xctest */,
);
name = Products;
@ -325,12 +325,12 @@
);
name = GhosttyTabs;
productName = GhosttyTabs;
productReference = A5001000 /* cmuxterm.app */;
productReference = A5001000 /* cmux.app */;
productType = "com.apple.product-type.application";
};
B9000005A1B2C3D4E5F60719 /* cmuxterm-cli */ = {
B9000005A1B2C3D4E5F60719 /* cmux-cli */ = {
isa = PBXNativeTarget;
buildConfigurationList = B9000007A1B2C3D4E5F60719 /* Build configuration list for PBXNativeTarget "cmuxterm-cli" */;
buildConfigurationList = B9000007A1B2C3D4E5F60719 /* Build configuration list for PBXNativeTarget "cmux-cli" */;
buildPhases = (
B9000006A1B2C3D4E5F60719 /* Sources */,
B900000CA1B2C3D4E5F60719 /* Frameworks */,
@ -339,9 +339,9 @@
);
dependencies = (
);
name = "cmuxterm-cli";
productName = cmuxterm;
productReference = B9000004A1B2C3D4E5F60719 /* cmuxterm */;
name = "cmux-cli";
productName = cmux;
productReference = B9000004A1B2C3D4E5F60719 /* cmux */;
productType = "com.apple.product-type.tool";
};
CB450DF0F0B3839599082C4D /* GhosttyTabsUITests */ = {
@ -390,7 +390,7 @@
projectRoot = "";
targets = (
A5001050 /* GhosttyTabs */,
B9000005A1B2C3D4E5F60719 /* cmuxterm-cli */,
B9000005A1B2C3D4E5F60719 /* cmux-cli */,
CB450DF0F0B3839599082C4D /* GhosttyTabsUITests */,
);
};
@ -450,7 +450,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B9000002A1B2C3D4E5F60719 /* cmuxterm.swift in Sources */,
B9000002A1B2C3D4E5F60719 /* cmux.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -464,7 +464,7 @@
};
B900000EA1B2C3D4E5F60719 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B9000005A1B2C3D4E5F60719 /* cmuxterm-cli */;
target = B9000005A1B2C3D4E5F60719 /* cmux-cli */;
targetProxy = B900000DA1B2C3D4E5F60719 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
@ -538,23 +538,23 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 33;
CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "cmuxterm DEV";
INFOPLIST_KEY_CFBundleName = "cmuxterm DEV";
INFOPLIST_KEY_CFBundleDisplayName = "cmux DEV";
INFOPLIST_KEY_CFBundleName = "cmux DEV";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainStoryboardFile = "";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
INFOPLIST_KEY_SUFeedURL = "https://github.com/manaflow-ai/cmuxterm/releases/latest/download/appcast.xml";
INFOPLIST_KEY_SUFeedURL = "https://github.com/manaflow-ai/cmux/releases/latest/download/appcast.xml";
INFOPLIST_KEY_SUPublicEDKey = "$(SPARKLE_PUBLIC_KEY)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.22.0;
MARKETING_VERSION = 1.23.0;
OTHER_LDFLAGS = (
"-lc++",
"-framework",
@ -568,8 +568,8 @@
"-framework",
Carbon,
);
PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.app.debug;
PRODUCT_NAME = "cmuxterm DEV";
PRODUCT_BUNDLE_IDENTIFIER = com.cmux.app.debug;
PRODUCT_NAME = "cmux DEV";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "cmux-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -583,23 +583,23 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 33;
CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = cmuxterm;
INFOPLIST_KEY_CFBundleName = cmuxterm;
INFOPLIST_KEY_CFBundleDisplayName = cmux;
INFOPLIST_KEY_CFBundleName = cmux;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainStoryboardFile = "";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
INFOPLIST_KEY_SUFeedURL = "https://github.com/manaflow-ai/cmuxterm/releases/latest/download/appcast.xml";
INFOPLIST_KEY_SUFeedURL = "https://github.com/manaflow-ai/cmux/releases/latest/download/appcast.xml";
INFOPLIST_KEY_SUPublicEDKey = "$(SPARKLE_PUBLIC_KEY)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.22.0;
MARKETING_VERSION = 1.23.0;
OTHER_LDFLAGS = (
"-lc++",
"-framework",
@ -614,8 +614,8 @@
Carbon,
);
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.app;
PRODUCT_NAME = cmuxterm;
PRODUCT_BUNDLE_IDENTIFIER = com.cmux.app;
PRODUCT_NAME = cmux;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "cmux-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -627,8 +627,8 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
MACOSX_DEPLOYMENT_TARGET = 13.0;
PRODUCT_NAME = cmuxterm;
PRODUCT_MODULE_NAME = cmuxterm_cli;
PRODUCT_NAME = cmux;
PRODUCT_MODULE_NAME = cmux_cli;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -640,8 +640,8 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
MACOSX_DEPLOYMENT_TARGET = 13.0;
PRODUCT_NAME = cmuxterm;
PRODUCT_MODULE_NAME = cmuxterm_cli;
PRODUCT_NAME = cmux;
PRODUCT_MODULE_NAME = cmux_cli;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
@ -652,12 +652,12 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 33;
CURRENT_PROJECT_VERSION = 34;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.22.0;
MARKETING_VERSION = 1.23.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.appuitests;
PRODUCT_BUNDLE_IDENTIFIER = com.cmux.appuitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_VERSION = 5.0;
@ -669,12 +669,12 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 33;
CURRENT_PROJECT_VERSION = 34;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.22.0;
MARKETING_VERSION = 1.23.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.appuitests;
PRODUCT_BUNDLE_IDENTIFIER = com.cmux.appuitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = GhosttyTabs;
@ -734,7 +734,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B9000007A1B2C3D4E5F60719 /* Build configuration list for PBXNativeTarget "cmuxterm-cli" */ = {
B9000007A1B2C3D4E5F60719 /* Build configuration list for PBXNativeTarget "cmux-cli" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B9000008A1B2C3D4E5F60719 /* Debug */,

View file

@ -3,7 +3,7 @@
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES">
<BuildActionEntries>
<BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmuxterm.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmux.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
@ -14,17 +14,17 @@
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmuxterm.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmux.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
</MacroExpansion>
</TestAction>
<LaunchAction buildConfiguration="Release" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" allowLocationSimulation="YES">
<BuildableProductRunnable runnableDebuggingMode="0">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmuxterm.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmux.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES">
<BuildableProductRunnable runnableDebuggingMode="0">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmuxterm.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="A5001050" BuildableName="cmux.app" BlueprintName="GhosttyTabs" ReferencedContainer="container:GhosttyTabs.xcodeproj"/>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction buildConfiguration="Release"/>

View file

@ -3,14 +3,14 @@ import Foundation
final class AutomationSocketUITests: XCTestCase {
private var socketPath = ""
private let defaultsDomain = "com.cmuxterm.app.debug"
private let defaultsDomain = "com.cmux.app.debug"
private let modeKey = "socketControlMode"
private let legacyKey = "socketControlEnabled"
override func setUp() {
super.setUp()
continueAfterFailure = false
socketPath = "/tmp/cmuxterm-debug-\(UUID().uuidString).sock"
socketPath = "/tmp/cmux-debug-\(UUID().uuidString).sock"
resetSocketDefaults()
removeSocketFile()
}
@ -75,7 +75,7 @@ final class AutomationSocketUITests: XCTestCase {
guard let entries = try? FileManager.default.contentsOfDirectory(atPath: tmpPath) else {
return nil
}
let matches = entries.filter { $0.hasPrefix("cmuxterm") && $0.hasSuffix(".sock") }
let matches = entries.filter { $0.hasPrefix("cmux") && $0.hasSuffix(".sock") }
if let debug = matches.first(where: { $0.contains("debug") }) {
return (tmpPath as NSString).appendingPathComponent(debug)
}

View file

@ -113,7 +113,7 @@ final class UpdatePillUITests: XCTestCase {
private func launchAppWithMockFeed(mode: String, version: String, timingPath: URL? = nil) -> XCUIApplication {
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_FEED_URL"] = "https://cmuxterm.test/appcast.xml"
app.launchEnvironment["CMUX_UI_TEST_FEED_URL"] = "https://cmux.test/appcast.xml"
app.launchEnvironment["CMUX_UI_TEST_FEED_MODE"] = mode
app.launchEnvironment["CMUX_UI_TEST_UPDATE_VERSION"] = version
app.launchEnvironment["CMUX_UI_TEST_AUTO_ALLOW_PERMISSION"] = "1"

View file

@ -1,14 +1,14 @@
<h1 align="center">cmuxterm</h1>
<h1 align="center">cmux</h1>
<p align="center">A Ghostty-based macOS terminal with vertical tabs and notifications for AI coding agents</p>
<p align="center">
<a href="https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg">
<img src="./docs/assets/macos-badge.png" alt="Download cmuxterm for macOS" width="180" />
<a href="https://github.com/manaflow-ai/cmux/releases/latest/download/cmux-macos.dmg">
<img src="./docs/assets/macos-badge.png" alt="Download cmux for macOS" width="180" />
</a>
</p>
<p align="center">
<img src="./docs/assets/screenshot.png" alt="cmuxterm screenshot" width="900" />
<img src="./docs/assets/screenshot.png" alt="cmux screenshot" width="900" />
</p>
## Features
@ -25,14 +25,14 @@
**Homebrew:**
```bash
brew tap manaflow-ai/cmuxterm
brew install --cask cmuxterm
brew tap manaflow-ai/cmux
brew install --cask cmux
```
Or [download the DMG](https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg) directly.
Or [download the DMG](https://github.com/manaflow-ai/cmux/releases/latest/download/cmux-macos.dmg) directly.
## Why cmuxterm?
## Why cmux?
Running multiple AI coding agents? cmuxterm helps you manage them. Instead of losing track of which terminal needs input, the notification panel shows you exactly where to look.
Running multiple AI coding agents? cmux helps you manage them. Instead of losing track of which terminal needs input, the notification panel shows you exactly where to look.
A native macOS app means it launches instantly, uses minimal RAM, and feels right at home on your Mac.

View file

@ -1,6 +1,6 @@
# vim:ft=zsh
#
# Compatibility shim: with the current integration model, cmuxterm restores
# Compatibility shim: with the current integration model, cmux restores
# ZDOTDIR in .zshenv so this file should never be reached. If it is, restore
# ZDOTDIR and behave like vanilla zsh by sourcing the user's .zlogin.

View file

@ -1,6 +1,6 @@
# vim:ft=zsh
#
# Compatibility shim: with the current integration model, cmuxterm restores
# Compatibility shim: with the current integration model, cmux restores
# ZDOTDIR in .zshenv so this file should never be reached. If it is, restore
# ZDOTDIR and behave like vanilla zsh by sourcing the user's .zprofile.

View file

@ -1,16 +1,16 @@
# vim:ft=zsh
#
# cmuxterm ZDOTDIR bootstrap for zsh.
# cmux ZDOTDIR bootstrap for zsh.
#
# GhosttyKit already uses a ZDOTDIR injection mechanism for zsh (setting ZDOTDIR
# to Ghostty's integration dir). cmuxterm also needs to run its integration, but
# to Ghostty's integration dir). cmux also needs to run its integration, but
# we must restore the user's real ZDOTDIR immediately so that:
# - /etc/zshrc sets HISTFILE relative to the real ZDOTDIR/HOME (shared history)
# - zsh loads the user's real .zprofile/.zshrc normally (no wrapper recursion)
#
# We restore ZDOTDIR from (in priority order):
# - GHOSTTY_ZSH_ZDOTDIR (set by GhosttyKit when it overwrote ZDOTDIR)
# - CMUX_ZSH_ZDOTDIR (set by cmuxterm when it overwrote a user-provided ZDOTDIR)
# - CMUX_ZSH_ZDOTDIR (set by cmux when it overwrote a user-provided ZDOTDIR)
# - unset (zsh treats unset ZDOTDIR as $HOME)
if [[ -n "${GHOSTTY_ZSH_ZDOTDIR+X}" ]]; then
@ -36,7 +36,7 @@ fi
[[ -r "$_cmux_ghostty" ]] && builtin source -- "$_cmux_ghostty"
fi
# Load cmuxterm integration (unless disabled)
# Load cmux integration (unless disabled)
if [[ "${CMUX_SHELL_INTEGRATION:-1}" != "0" && -n "${CMUX_SHELL_INTEGRATION_DIR:-}" ]]; then
builtin typeset _cmux_integ="$CMUX_SHELL_INTEGRATION_DIR/cmux-zsh-integration.zsh"
[[ -r "$_cmux_integ" ]] && builtin source -- "$_cmux_integ"

View file

@ -1,6 +1,6 @@
# vim:ft=zsh
#
# Compatibility shim: with the current integration model, cmuxterm restores
# Compatibility shim: with the current integration model, cmux restores
# ZDOTDIR in .zshenv so this file should never be reached. If it is, restore
# ZDOTDIR and behave like vanilla zsh by sourcing the user's .zshrc.

View file

@ -1,4 +1,4 @@
# cmuxterm shell integration for bash
# cmux shell integration for bash
_cmux_send() {
local payload="$1"
@ -10,7 +10,7 @@ _cmux_send() {
# Some nc builds don't support unix sockets, but keep as a last-ditch fallback.
#
# Important: macOS/BSD nc will often wait for the peer to close the socket
# after it has finished writing. cmuxterm keeps the connection open, so
# after it has finished writing. cmux keeps the connection open, so
# a plain `nc -U` can hang indefinitely and leak background processes.
#
# Prefer flags that guarantee we exit after sending, and fall back to a

View file

@ -1,4 +1,4 @@
# cmuxterm shell integration for zsh
# cmux shell integration for zsh
# Injected automatically — do not source manually
_cmux_send() {
@ -11,7 +11,7 @@ _cmux_send() {
# Some nc builds don't support unix sockets, but keep as a last-ditch fallback.
#
# Important: macOS/BSD nc will often wait for the peer to close the socket
# after it has finished writing. cmuxterm keeps the connection open, so
# after it has finished writing. cmux keeps the connection open, so
# a plain `nc -U` can hang indefinitely and leak background processes.
#
# Prefer flags that guarantee we exit after sending, and fall back to a

View file

@ -1,14 +1,14 @@
# cmuxterm terminfo overlay
# cmux terminfo overlay
cmuxterm ships Ghostty's `xterm-ghostty` terminfo entry, but the embedded
renderer in cmuxterm has differed from Ghostty's app renderer in how it treats
cmux ships Ghostty's `xterm-ghostty` terminfo entry, but the embedded
renderer in cmux has differed from Ghostty's app renderer in how it treats
the "bright" SGR 90-97/100-107 sequences.
This overlay patches the terminfo capabilities so that `tput setaf 8` (and
similar "bright" colors) uses 256-color indexed sequences (`38;5;<n>m` /
`48;5;<n>m`) rather than SGR 90-97/100-107. This avoids relying on bright SGR
handling and fixes zsh-autosuggestions (default `fg=8`) visibility issues in
cmuxterm.
cmux.
The build phase `Copy Ghostty Resources` overlays this directory onto the app
bundle's `Contents/Resources/terminfo` after copying Ghostty's resources.

View file

@ -47,9 +47,9 @@ struct SocketControlSettings {
return override
}
#if DEBUG
return "/tmp/cmuxterm-debug.sock"
return "/tmp/cmux-debug.sock"
#else
return "/tmp/cmuxterm.sock"
return "/tmp/cmux.sock"
#endif
}

View file

@ -759,13 +759,13 @@ class TabManager: ObservableObject {
}
private func windowTitle(for tab: Tab?) -> String {
guard let tab else { return "cmuxterm" }
guard let tab else { return "cmux" }
let trimmedTitle = tab.title.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedTitle.isEmpty {
return trimmedTitle
}
let trimmedDirectory = tab.currentDirectory.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmedDirectory.isEmpty ? "cmuxterm" : trimmedDirectory
return trimmedDirectory.isEmpty ? "cmux" : trimmedDirectory
}
func focusTab(_ tabId: UUID, surfaceId: UUID? = nil, suppressFlash: Bool = false) {

View file

@ -7,7 +7,7 @@ import Foundation
class TerminalController {
static let shared = TerminalController()
private var socketPath = "/tmp/cmuxterm.sock"
private var socketPath = "/tmp/cmux.sock"
private var serverSocket: Int32 = -1
private var isRunning = false
private var clientHandlers: [Int32: Thread] = [:]

View file

@ -34,8 +34,8 @@ struct TerminalNotification: Identifiable, Hashable {
final class TerminalNotificationStore: ObservableObject {
static let shared = TerminalNotificationStore()
static let categoryIdentifier = "com.cmuxterm.app.userNotification"
static let actionShowIdentifier = "com.cmuxterm.app.userNotification.show"
static let categoryIdentifier = "com.cmux.app.userNotification"
static let actionShowIdentifier = "com.cmux.app.userNotification.show"
@Published private(set) var notifications: [TerminalNotification] = []
@ -176,7 +176,7 @@ final class TerminalNotificationStore: ObservableObject {
let content = UNMutableNotificationContent()
let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String
?? "cmuxterm"
?? "cmux"
content.title = notification.title.isEmpty ? appName : notification.title
content.subtitle = notification.subtitle
content.body = notification.body
@ -242,8 +242,8 @@ final class TerminalNotificationStore: ObservableObject {
self.hasPromptedForSettings = true
let alert = NSAlert()
alert.messageText = "Enable Notifications for cmuxterm"
alert.informativeText = "Notifications are disabled for cmuxterm. Enable them in System Settings to see alerts."
alert.messageText = "Enable Notifications for cmux"
alert.informativeText = "Notifications are disabled for cmux. Enable them in System Settings to see alerts."
alert.addButton(withTitle: "Open Settings")
alert.addButton(withTitle: "Not Now")
let response = alert.runModal()

View file

@ -3,7 +3,7 @@ import Cocoa
import Combine
import SwiftUI
/// Controller for managing Sparkle updates in cmuxterm.
/// Controller for managing Sparkle updates in cmux.
class UpdateController {
private(set) var updater: SPUUpdater
private let userDriver: UpdateDriver

View file

@ -12,7 +12,7 @@ extension UpdateDriver: SPUUpdaterDelegate {
}
#endif
let infoURL = Bundle.main.object(forInfoDictionaryKey: "SUFeedURL") as? String
let fallback = "https://github.com/manaflow-ai/cmuxterm/releases/latest/download/appcast.xml"
let fallback = "https://github.com/manaflow-ai/cmux/releases/latest/download/appcast.xml"
let feedURLString = (infoURL?.isEmpty == false) ? infoURL! : fallback
recordFeedURLString(feedURLString, usedFallback: feedURLString == fallback)
return feedURLString

View file

@ -56,7 +56,7 @@ class UpdateDriver: NSObject, SPUUserDriver {
}
func showUpdateReleaseNotes(with downloadData: SPUDownloadData) {
// cmuxterm uses Sparkle's UI for release notes links instead.
// cmux uses Sparkle's UI for release notes links instead.
}
func showUpdateReleaseNotesFailedToDownloadWithError(_ error: any Error) {

View file

@ -4,7 +4,7 @@ import AppKit
final class UpdateLogStore {
static let shared = UpdateLogStore()
private let queue = DispatchQueue(label: "cmuxterm.update.log")
private let queue = DispatchQueue(label: "cmux.update.log")
private var entries: [String] = []
private let maxEntries = 200
private let maxFileSize: UInt64 = 256 * 1024 // 256 KB
@ -16,7 +16,7 @@ final class UpdateLogStore {
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let logsDir = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
?? FileManager.default.temporaryDirectory
logURL = logsDir.appendingPathComponent("Logs/cmuxterm-update.log")
logURL = logsDir.appendingPathComponent("Logs/cmux-update.log")
ensureLogFile()
}
@ -84,7 +84,7 @@ final class UpdateLogStore {
final class FocusLogStore {
static let shared = FocusLogStore()
private let queue = DispatchQueue(label: "cmuxterm.focus.log")
private let queue = DispatchQueue(label: "cmux.focus.log")
private var entries: [String] = []
private let maxEntries = 400
private let maxFileSize: UInt64 = 256 * 1024 // 256 KB
@ -96,7 +96,7 @@ final class FocusLogStore {
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let logsDir = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
?? FileManager.default.temporaryDirectory
logURL = logsDir.appendingPathComponent("Logs/cmuxterm-focus.log")
logURL = logsDir.appendingPathComponent("Logs/cmux-focus.log")
ensureLogFile()
}

View file

@ -52,7 +52,7 @@ fileprivate struct PermissionRequestView: View {
Text("Enable automatic updates?")
.font(.system(size: 13, weight: .semibold))
Text("cmuxterm can automatically check for updates in the background.")
Text("cmux can automatically check for updates in the background.")
.font(.system(size: 11))
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)

View file

@ -61,13 +61,13 @@ enum UpdateTestSupport {
private static func makeAppcastItem(displayVersion: String) -> SUAppcastItem? {
let enclosure: [String: Any] = [
"url": "https://example.com/cmuxterm.zip",
"url": "https://example.com/cmux.zip",
"length": "1024",
"sparkle:version": displayVersion,
"sparkle:shortVersionString": displayVersion,
]
let dict: [String: Any] = [
"title": "cmuxterm \(displayVersion)",
"title": "cmux \(displayVersion)",
"enclosure": enclosure,
]
return SUAppcastItem(dictionary: dict)

View file

@ -2,9 +2,9 @@
import Foundation
final class UpdateTestURLProtocol: URLProtocol {
static let host = "cmuxterm.test"
static let host = "cmux.test"
static let appcastPath = "/appcast.xml"
static let updatePath = "/cmuxterm-test.zip"
static let updatePath = "/cmux-test.zip"
private static var isRegistered = false
private static let registrationLock = NSLock()
@ -84,7 +84,7 @@ final class UpdateTestURLProtocol: URLProtocol {
} else {
item = """
<item>
<title>cmuxterm \(version)</title>
<title>cmux \(version)</title>
<sparkle:version>\(version)</sparkle:version>
<sparkle:shortVersionString>\(version)</sparkle:shortVersionString>
<enclosure url="\(updateURL)" length="\(updateLength)" type="application/octet-stream" />
@ -98,7 +98,7 @@ final class UpdateTestURLProtocol: URLProtocol {
xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>cmuxterm Test Updates</title>
<title>cmux Test Updates</title>
<link>https://\(host)</link>
<description>Test updates feed</description>
<language>en</language>
@ -111,7 +111,7 @@ final class UpdateTestURLProtocol: URLProtocol {
}
private static func updateArchiveData() -> Data {
Data("cmuxterm test update".utf8)
Data("cmux test update".utf8)
}
}
#endif

View file

@ -216,13 +216,13 @@ class UpdateViewModel: ObservableObject {
if let networkError = networkError(from: nsError) {
switch networkError.code {
case NSURLErrorNotConnectedToInternet:
return "cmuxterm cant reach the update server. Check your internet connection and try again."
return "cmux can't reach the update server. Check your internet connection and try again."
case NSURLErrorTimedOut:
return "The update server took too long to respond. Try again in a moment."
case NSURLErrorCannotFindHost:
return "The update server cant be found. Check your connection or try again later."
case NSURLErrorCannotConnectToHost:
return "cmuxterm couldnt connect to the update server. Check your connection or try again later."
return "cmux couldn't connect to the update server. Check your connection or try again later."
case NSURLErrorNetworkConnectionLost:
return "The network connection was lost while checking for updates. Try again."
case NSURLErrorSecureConnectionFailed,
@ -238,7 +238,7 @@ class UpdateViewModel: ObservableObject {
if nsError.domain == SUSparkleErrorDomain {
switch nsError.code {
case 2001:
return "cmuxterm couldn't download the update feed. Check your connection and try again."
return "cmux couldn't download the update feed. Check your connection and try again."
case 1000, 1002:
return "The update feed could not be read. Please try again later."
case 4:
@ -248,7 +248,7 @@ class UpdateViewModel: ObservableObject {
case 1, 2, 3001, 3002:
return "The update's signature could not be verified. Please try again later."
case 1003, 1005, 4005:
return "Move cmuxterm into Applications and relaunch to enable updates."
return "Move cmux into Applications and relaunch to enable updates."
default:
break
}
@ -437,7 +437,7 @@ enum UpdateState: Equatable {
if let semver = Self.extractSemanticVersion(from: version) {
let tag = semver.hasPrefix("v") ? semver : "v\(semver)"
if let url = URL(string: "https://github.com/manaflow-ai/cmuxterm/releases/tag/\(tag)") {
if let url = URL(string: "https://github.com/manaflow-ai/cmux/releases/tag/\(tag)") {
self = .tagged(url)
return
}
@ -447,7 +447,7 @@ enum UpdateState: Equatable {
return nil
}
if let url = URL(string: "https://github.com/manaflow-ai/cmuxterm/commit/\(newHash)") {
if let url = URL(string: "https://github.com/manaflow-ai/cmux/commit/\(newHash)") {
self = .commit(url)
} else {
return nil

View file

@ -128,7 +128,7 @@ struct cmuxApp: App {
.windowResizability(.contentMinSize)
.commands {
CommandGroup(replacing: .appInfo) {
Button("About cmuxterm") {
Button("About cmux") {
showAboutPanel()
}
Button("Ghostty Settings…") {
@ -462,7 +462,7 @@ private final class SidebarDebugWindowController: NSWindowController, NSWindowDe
private struct AboutPanelView: View {
@Environment(\.openURL) private var openURL
private let githubURL = URL(string: "https://github.com/manaflow-ai/cmuxterm")
private let githubURL = URL(string: "https://github.com/manaflow-ai/cmux")
private let docsURL = URL(string: "https://term.cmux.dev")
private var version: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String }
@ -486,7 +486,7 @@ private struct AboutPanelView: View {
VStack(alignment: .center, spacing: 32) {
VStack(alignment: .center, spacing: 8) {
Text("cmuxterm")
Text("cmux")
.bold()
.font(.title)
Text("A Ghostty-based terminal with vertical tabs\nand a notification panel for macOS.")
@ -507,7 +507,7 @@ private struct AboutPanelView: View {
}
let commitText = commit ?? ""
let commitURL = commit.flatMap { hash in
URL(string: "https://github.com/manaflow-ai/cmuxterm/commit/\(hash)")
URL(string: "https://github.com/manaflow-ai/cmux/commit/\(hash)")
}
AboutPropertyRow(label: "Commit", text: commitText, url: commitURL)
}

View file

@ -8,7 +8,7 @@
- Opens a new terminal
- Shows user the diff to their config file (claude.json, opencode config, codex config, etc.)
- Prompts user to type 'y' to confirm
- Implement as part of `cmuxterm` CLI, menubar just triggers the CLI command
- Implement as part of `cmux` CLI, menubar just triggers the CLI command
## Additional Integrations
- [ ] Codex integration

View file

@ -1,6 +1,6 @@
# cmuxterm Documentation
# cmux Documentation
Documentation website for [cmuxterm](https://github.com/manaflow-ai/cmuxterm), built with [Fumadocs](https://fumadocs.vercel.app) and Next.js.
Documentation website for [cmux](https://github.com/manaflow-ai/cmux), built with [Fumadocs](https://fumadocs.vercel.app) and Next.js.
## Development

View file

@ -7,7 +7,7 @@ export default function Layout({ children }: { children: ReactNode }) {
<DocsLayout
tree={source.pageTree}
nav={{
title: 'cmuxterm',
title: 'cmux',
url: '/',
}}
sidebar={{

View file

@ -8,8 +8,8 @@ const inter = Inter({
});
export const metadata = {
title: 'cmuxterm Documentation',
description: 'Documentation for cmuxterm - A Ghostty-based terminal for AI agents',
title: 'cmux Documentation',
description: 'Documentation for cmux - A Ghostty-based terminal for AI agents',
};
export default function Layout({ children }: { children: ReactNode }) {

View file

@ -1,9 +1,18 @@
---
title: Changelog
description: Release notes and version history for cmuxterm
description: Release notes and version history for cmux
---
All notable changes to cmuxterm are documented here.
All notable changes to cmux are documented here.
## [1.23.0] - 2026-02-09
### Changed
- Rename app from cmuxterm to cmux — new app name, bundle ID, socket paths, Homebrew tap, and CLI binary name
- Sidebar now shows tab status as text instead of colored dots, with instant git HEAD change detection
### Fixed
- CLI `set-status` command not properly quoting values or routing `--tab` flag
## [1.22.0] - 2026-02-09
@ -117,7 +126,7 @@ All notable changes to cmuxterm are documented here.
### Added
- Sentry SDK for crash reporting
- Documentation site with Fumadocs
- Homebrew installation support (`brew install --cask cmuxterm`)
- Homebrew installation support (`brew install --cask cmux`)
- Auto-update Homebrew cask on release
### Fixed
@ -163,7 +172,7 @@ All notable changes to cmuxterm are documented here.
## [1.4.0] - 2025-01-28
### Added
- cmuxterm CLI with socket control modes
- cmux CLI with socket control modes
- NSPopover-based notifications
### Fixed
@ -187,7 +196,7 @@ All notable changes to cmuxterm are documented here.
- Update UI error details and pill visibility
### Changed
- Renamed app to cmuxterm
- Renamed app to cmux
- Improved CI UI test stability
## [1.1.0] - 2025-01-28

View file

@ -1,38 +1,38 @@
---
title: Claude Code Hooks
description: Set up notifications for Claude Code using cmuxterm hooks
description: Set up notifications for Claude Code using cmux hooks
---
# Claude Code Hooks
cmuxterm integrates with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) via hooks to notify you when tasks complete or when Claude needs attention.
cmux integrates with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) via hooks to notify you when tasks complete or when Claude needs attention.
## Detecting cmuxterm
## Detecting cmux
Before sending notifications, you should detect if you're running inside cmuxterm to avoid conflicts with other terminals.
Before sending notifications, you should detect if you're running inside cmux to avoid conflicts with other terminals.
### Detection Methods
**1. Check for the socket:**
```bash
[ -S /tmp/cmuxterm.sock ] && echo "In cmuxterm"
[ -S /tmp/cmux.sock ] && echo "In cmux"
```
**2. Check for the CLI:**
```bash
command -v cmuxterm &>/dev/null && echo "cmuxterm available"
command -v cmux &>/dev/null && echo "cmux available"
```
**3. Check the TERM_PROGRAM environment variable:**
```bash
[ "$TERM_PROGRAM" = "ghostty" ] && [ -S /tmp/cmuxterm.sock ] && echo "In cmuxterm"
[ "$TERM_PROGRAM" = "ghostty" ] && [ -S /tmp/cmux.sock ] && echo "In cmux"
```
<Callout type="info">
cmuxterm sets `TERM_PROGRAM=ghostty` since it's built on Ghostty. Use the socket check to distinguish from regular Ghostty.
cmux sets `TERM_PROGRAM=ghostty` since it's built on Ghostty. Use the socket check to distinguish from regular Ghostty.
</Callout>
## Setting Up Hooks
@ -46,12 +46,12 @@ Claude Code supports hooks that run on specific events. Add these to your `~/.cl
{
"matcher": "Task",
"hooks": [
"/path/to/cmuxterm-notify.sh"
"/path/to/cmux-notify.sh"
]
}
],
"Stop": [
"/path/to/cmuxterm-notify.sh"
"/path/to/cmux-notify.sh"
]
}
}
@ -59,14 +59,14 @@ Claude Code supports hooks that run on specific events. Add these to your `~/.cl
## Notification Hook Script
Create a script that checks for cmuxterm and sends notifications:
Create a script that checks for cmux and sends notifications:
```bash
#!/bin/bash
# ~/.claude/hooks/cmuxterm-notify.sh
# ~/.claude/hooks/cmux-notify.sh
# Only proceed if we're in cmuxterm
if [ ! -S /tmp/cmuxterm.sock ]; then
# Only proceed if we're in cmux
if [ ! -S /tmp/cmux.sock ]; then
exit 0
fi
@ -80,14 +80,14 @@ SESSION=$(echo "$EVENT" | jq -r '.session_id // ""' | cut -c1-8)
case "$EVENT_TYPE" in
"Stop")
cmuxterm notify \
cmux notify \
--title "Claude Code" \
--subtitle "Task Complete" \
--body "Session $SESSION finished"
;;
"PostToolUse")
if [ "$TOOL_NAME" = "Task" ]; then
cmuxterm notify \
cmux notify \
--title "Claude Code" \
--subtitle "Agent Finished" \
--body "Task agent completed in session $SESSION"
@ -99,7 +99,7 @@ esac
Make it executable:
```bash
chmod +x ~/.claude/hooks/cmuxterm-notify.sh
chmod +x ~/.claude/hooks/cmux-notify.sh
```
## Example Configurations
@ -112,7 +112,7 @@ Get notified whenever Claude Code finishes a task:
{
"hooks": {
"Stop": [
"~/.claude/hooks/cmuxterm-notify.sh"
"~/.claude/hooks/cmux-notify.sh"
]
}
}
@ -128,7 +128,7 @@ Only notify for Task tool completions (agent subprocesses):
"PostToolUse": [
{
"matcher": "Task",
"hooks": ["~/.claude/hooks/cmuxterm-notify.sh"]
"hooks": ["~/.claude/hooks/cmux-notify.sh"]
}
]
}
@ -141,9 +141,9 @@ Add error notifications:
```bash
#!/bin/bash
# cmuxterm-notify.sh with error handling
# cmux-notify.sh with error handling
if [ ! -S /tmp/cmuxterm.sock ]; then
if [ ! -S /tmp/cmux.sock ]; then
exit 0
fi
@ -152,11 +152,11 @@ EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
ERROR=$(echo "$EVENT" | jq -r '.error // ""')
if [ -n "$ERROR" ] && [ "$ERROR" != "null" ]; then
cmuxterm notify \
cmux notify \
--title "Claude Code Error" \
--body "$ERROR"
elif [ "$EVENT_TYPE" = "Stop" ]; then
cmuxterm notify \
cmux notify \
--title "Claude Code" \
--body "Task complete"
fi
@ -168,20 +168,20 @@ Only notify if the terminal is not focused:
```bash
#!/bin/bash
# cmuxterm-notify.sh with focus detection
# cmux-notify.sh with focus detection
if [ ! -S /tmp/cmuxterm.sock ]; then
if [ ! -S /tmp/cmux.sock ]; then
exit 0
fi
# cmuxterm automatically suppresses notifications for focused tabs,
# cmux automatically suppresses notifications for focused tabs,
# so we can always send - it will handle suppression for us
EVENT=$(cat)
EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
if [ "$EVENT_TYPE" = "Stop" ]; then
cmuxterm notify \
cmux notify \
--title "Claude Code" \
--subtitle "Ready" \
--body "Waiting for your input"
@ -196,7 +196,7 @@ If you prefer not to use the CLI, you can emit OSC sequences directly:
#!/bin/bash
# Direct OSC notification (no CLI needed)
if [ ! -S /tmp/cmuxterm.sock ]; then
if [ ! -S /tmp/cmux.sock ]; then
exit 0
fi
@ -220,12 +220,12 @@ mkdir -p ~/.claude/hooks
2. Create the notification script:
```bash
cat > ~/.claude/hooks/cmuxterm-notify.sh << 'EOF'
cat > ~/.claude/hooks/cmux-notify.sh << 'EOF'
#!/bin/bash
# cmuxterm notification hook for Claude Code
# cmux notification hook for Claude Code
# Skip if not in cmuxterm
[ -S /tmp/cmuxterm.sock ] || exit 0
# Skip if not in cmux
[ -S /tmp/cmux.sock ] || exit 0
EVENT=$(cat)
EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
@ -233,14 +233,14 @@ TOOL=$(echo "$EVENT" | jq -r '.tool_name // ""')
case "$EVENT_TYPE" in
"Stop")
cmuxterm notify --title "Claude Code" --body "Session complete"
cmux notify --title "Claude Code" --body "Session complete"
;;
"PostToolUse")
[ "$TOOL" = "Task" ] && cmuxterm notify --title "Claude Code" --body "Agent finished"
[ "$TOOL" = "Task" ] && cmux notify --title "Claude Code" --body "Agent finished"
;;
esac
EOF
chmod +x ~/.claude/hooks/cmuxterm-notify.sh
chmod +x ~/.claude/hooks/cmux-notify.sh
```
3. Configure Claude Code:
@ -249,11 +249,11 @@ chmod +x ~/.claude/hooks/cmuxterm-notify.sh
cat > ~/.claude/settings.json << 'EOF'
{
"hooks": {
"Stop": ["~/.claude/hooks/cmuxterm-notify.sh"],
"Stop": ["~/.claude/hooks/cmux-notify.sh"],
"PostToolUse": [
{
"matcher": "Task",
"hooks": ["~/.claude/hooks/cmuxterm-notify.sh"]
"hooks": ["~/.claude/hooks/cmux-notify.sh"]
}
]
}
@ -263,4 +263,4 @@ EOF
4. Restart Claude Code to apply the hooks.
Now you'll receive desktop notifications in cmuxterm whenever Claude Code finishes a task or needs attention.
Now you'll receive desktop notifications in cmux whenever Claude Code finishes a task or needs attention.

View file

@ -1,19 +1,19 @@
---
title: CLI Reference
description: Command-line interface for cmuxterm
description: Command-line interface for cmux
---
# CLI Reference
cmuxterm includes a command-line tool for controlling the terminal from scripts and other tools.
cmux includes a command-line tool for controlling the terminal from scripts and other tools.
## Installation
The CLI is bundled with cmuxterm. Inside cmuxterm terminals, it's available automatically. For external use:
The CLI is bundled with cmux. Inside cmux terminals, it's available automatically. For external use:
```bash
# Create symlink to CLI
sudo ln -sf "/Applications/cmuxterm.app/Contents/MacOS/cmuxterm" /usr/local/bin/cmuxterm
sudo ln -sf "/Applications/cmux.app/Contents/MacOS/cmux" /usr/local/bin/cmux
```
## Global Options
@ -42,8 +42,8 @@ sudo ln -sf "/Applications/cmuxterm.app/Contents/MacOS/cmuxterm" /usr/local/bin/
List all open tabs.
```bash
cmuxterm list-tabs
cmuxterm list-tabs --json
cmux list-tabs
cmux list-tabs --json
```
#### new-tab
@ -51,7 +51,7 @@ cmuxterm list-tabs --json
Create a new tab.
```bash
cmuxterm new-tab
cmux new-tab
```
#### select-tab
@ -59,7 +59,7 @@ cmuxterm new-tab
Switch to a specific tab.
```bash
cmuxterm select-tab --tab <tab-id>
cmux select-tab --tab <tab-id>
```
#### current-tab
@ -67,8 +67,8 @@ cmuxterm select-tab --tab <tab-id>
Get the current tab info.
```bash
cmuxterm current-tab
cmuxterm current-tab --json
cmux current-tab
cmux current-tab --json
```
#### close-tab
@ -76,7 +76,7 @@ cmuxterm current-tab --json
Close a tab.
```bash
cmuxterm close-tab --tab <tab-id>
cmux close-tab --tab <tab-id>
```
### Split Management
@ -86,10 +86,10 @@ cmuxterm close-tab --tab <tab-id>
Create a new split pane.
```bash
cmuxterm new-split right
cmuxterm new-split down
cmuxterm new-split left
cmuxterm new-split up
cmux new-split right
cmux new-split down
cmux new-split left
cmux new-split up
```
#### list-panels
@ -97,8 +97,8 @@ cmuxterm new-split up
List panels in the current tab.
```bash
cmuxterm list-panels
cmuxterm list-panels --json
cmux list-panels
cmux list-panels --json
```
#### focus-panel
@ -106,7 +106,7 @@ cmuxterm list-panels --json
Focus a specific panel.
```bash
cmuxterm focus-panel --panel <panel-id>
cmux focus-panel --panel <panel-id>
```
### Input Commands
@ -116,8 +116,8 @@ cmuxterm focus-panel --panel <panel-id>
Send text to the terminal.
```bash
cmuxterm send "echo hello"
cmuxterm send "ls -la\n" # Include newline to execute
cmux send "echo hello"
cmux send "ls -la\n" # Include newline to execute
```
#### send-key
@ -125,9 +125,9 @@ cmuxterm send "ls -la\n" # Include newline to execute
Send a key press.
```bash
cmuxterm send-key enter
cmuxterm send-key tab
cmuxterm send-key escape
cmux send-key enter
cmux send-key tab
cmux send-key escape
```
#### send-panel
@ -135,7 +135,7 @@ cmuxterm send-key escape
Send text to a specific panel.
```bash
cmuxterm send-panel --panel <panel-id> "command"
cmux send-panel --panel <panel-id> "command"
```
#### send-key-panel
@ -143,7 +143,7 @@ cmuxterm send-panel --panel <panel-id> "command"
Send a key press to a specific panel.
```bash
cmuxterm send-key-panel --panel <panel-id> enter
cmux send-key-panel --panel <panel-id> enter
```
### Notifications
@ -153,8 +153,8 @@ cmuxterm send-key-panel --panel <panel-id> enter
Send a notification.
```bash
cmuxterm notify --title "Title" --body "Message body"
cmuxterm notify --title "Title" --subtitle "Subtitle" --body "Body"
cmux notify --title "Title" --body "Message body"
cmux notify --title "Title" --subtitle "Subtitle" --body "Body"
```
#### list-notifications
@ -162,8 +162,8 @@ cmuxterm notify --title "Title" --subtitle "Subtitle" --body "Body"
List all notifications.
```bash
cmuxterm list-notifications
cmuxterm list-notifications --json
cmux list-notifications
cmux list-notifications --json
```
#### clear-notifications
@ -171,17 +171,17 @@ cmuxterm list-notifications --json
Clear all notifications.
```bash
cmuxterm clear-notifications
cmux clear-notifications
```
### Utility
#### ping
Check if cmuxterm is running and responsive.
Check if cmux is running and responsive.
```bash
cmuxterm ping
cmux ping
```
## Examples
@ -192,9 +192,9 @@ cmuxterm ping
#!/bin/bash
npm run build
if [ $? -eq 0 ]; then
cmuxterm notify --title "✓ Build Success" --body "Ready to deploy"
cmux notify --title "✓ Build Success" --body "Ready to deploy"
else
cmuxterm notify --title "✗ Build Failed" --body "Check the logs"
cmux notify --title "✗ Build Failed" --body "Check the logs"
fi
```
@ -203,14 +203,14 @@ fi
```bash
#!/bin/bash
# Create a new tab
result=$(cmuxterm new-tab --json)
result=$(cmux new-tab --json)
tab_id=$(echo "$result" | jq -r '.id')
# Select the new tab
cmuxterm select-tab --tab "$tab_id"
cmux select-tab --tab "$tab_id"
# Send a command
cmuxterm send "npm run dev\n"
cmux send "npm run dev\n"
```
### Monitor Multiple Tabs
@ -218,5 +218,5 @@ cmuxterm send "npm run dev\n"
```bash
#!/bin/bash
# List all tabs and their directories
cmuxterm list-tabs --json | jq -r '.tabs[] | "\(.title): \(.directory)"'
cmux list-tabs --json | jq -r '.tabs[] | "\(.title): \(.directory)"'
```

View file

@ -1,15 +1,15 @@
---
title: Configuration
description: Configure cmuxterm appearance and behavior
description: Configure cmux appearance and behavior
---
# Configuration
cmuxterm reads configuration from Ghostty config files, giving you familiar options if you're coming from Ghostty.
cmux reads configuration from Ghostty config files, giving you familiar options if you're coming from Ghostty.
## Config File Locations
cmuxterm looks for configuration in these locations (in order):
cmux looks for configuration in these locations (in order):
1. `~/.config/ghostty/config`
2. `~/Library/Application Support/com.mitchellh.ghostty/config`
@ -76,7 +76,7 @@ working-directory = ~/Projects
## App Settings
In-app settings are available via **cmuxterm → Settings** (⌘,):
In-app settings are available via **cmux → Settings** (⌘,):
### Theme Mode

View file

@ -1,11 +1,11 @@
---
title: Environment Variables
description: Environment variables for configuring cmuxterm
description: Environment variables for configuring cmux
---
# Environment Variables
cmuxterm uses environment variables for configuration and integration.
cmux uses environment variables for configuration and integration.
## Socket Control
@ -14,10 +14,10 @@ cmuxterm uses environment variables for configuration and integration.
Override the default socket path.
```bash
export CMUX_SOCKET_PATH=/custom/path/cmuxterm.sock
export CMUX_SOCKET_PATH=/custom/path/cmux.sock
```
Default: `/tmp/cmuxterm.sock` (release) or `/tmp/cmuxterm-debug.sock` (debug)
Default: `/tmp/cmux.sock` (release) or `/tmp/cmux-debug.sock` (debug)
### CMUX_SOCKET_ENABLE
@ -48,10 +48,10 @@ Default tab ID for CLI commands.
```bash
export CMUX_TAB_ID=abc123
cmuxterm send "hello" # Sends to tab abc123
cmux send "hello" # Sends to tab abc123
```
cmuxterm automatically sets this in each terminal session.
cmux automatically sets this in each terminal session.
### CMUX_PANEL_ID
@ -59,14 +59,14 @@ Default panel ID for CLI commands.
```bash
export CMUX_PANEL_ID=xyz789
cmuxterm send-panel "hello" # Sends to panel xyz789
cmux send-panel "hello" # Sends to panel xyz789
```
cmuxterm automatically sets this for each split pane.
cmux automatically sets this for each split pane.
## Terminal Environment
cmuxterm sets these variables in terminal sessions:
cmux sets these variables in terminal sessions:
### TERM
@ -85,7 +85,7 @@ TERM_PROGRAM=ghostty
```
<Callout type="info">
cmuxterm sets this to `ghostty` since it's built on Ghostty. Use the socket check to distinguish from regular Ghostty.
cmux sets this to `ghostty` since it's built on Ghostty. Use the socket check to distinguish from regular Ghostty.
</Callout>
### GHOSTTY_RESOURCES_DIR
@ -124,25 +124,25 @@ export CMUX_COMMIT=abc1234
## Detection Script
Check if running inside cmuxterm:
Check if running inside cmux:
```bash
#!/bin/bash
is_cmuxterm() {
# Check for cmuxterm socket
if [ -S "${CMUX_SOCKET_PATH:-/tmp/cmuxterm.sock}" ]; then
is_cmux() {
# Check for cmux socket
if [ -S "${CMUX_SOCKET_PATH:-/tmp/cmux.sock}" ]; then
return 0
fi
return 1
}
if is_cmuxterm; then
echo "Running in cmuxterm"
if is_cmux; then
echo "Running in cmux"
echo "Tab ID: ${CMUX_TAB_ID:-unknown}"
echo "Panel ID: ${CMUX_PANEL_ID:-unknown}"
else
echo "Not in cmuxterm"
echo "Not in cmux"
fi
```
@ -151,15 +151,15 @@ fi
Add to your `~/.bashrc` or `~/.zshrc`:
```bash
# cmuxterm integration
if [ -S "${CMUX_SOCKET_PATH:-/tmp/cmuxterm.sock}" ]; then
# We're in cmuxterm
# cmux integration
if [ -S "${CMUX_SOCKET_PATH:-/tmp/cmux.sock}" ]; then
# We're in cmux
# Function to notify on long commands
notify_done() {
"$@"
local exit_code=$?
cmuxterm notify --title "Command Complete" --body "$1"
cmux notify --title "Command Complete" --body "$1"
return $exit_code
}

View file

@ -1,13 +1,13 @@
---
title: Introduction
description: cmuxterm - A lightweight native macOS terminal with vertical tabs and notifications for AI coding agents
description: cmux - A lightweight native macOS terminal with vertical tabs and notifications for AI coding agents
---
# cmuxterm
# cmux
cmuxterm is a lightweight, native macOS terminal built on Ghostty for managing multiple AI coding agents. It features vertical tabs, a notification panel, and a socket-based control API.
cmux is a lightweight, native macOS terminal built on Ghostty for managing multiple AI coding agents. It features vertical tabs, a notification panel, and a socket-based control API.
## Why cmuxterm?
## Why cmux?
- **Native macOS app** - Built with Swift and AppKit, not Electron. Fast startup, low memory usage, and native look and feel.
- **Lightweight** - Small binary, minimal resource footprint. No bundled browser engine.
@ -19,7 +19,7 @@ When running AI coding agents like Claude Code, Codex, or similar tools, you nee
- **Know when agents need attention** - Agents waiting for input should notify you
- **Automate terminal control** - Programmatically create tabs, send input, and more
cmuxterm solves these with:
cmux solves these with:
- **Vertical sidebar tabs** - All terminals visible at a glance
- **OSC 99/777 notifications** - Desktop alerts when agents need you
@ -28,11 +28,11 @@ cmuxterm solves these with:
## Quick Start
```bash
brew tap manaflow-ai/cmuxterm
brew install --cask cmuxterm
brew tap manaflow-ai/cmux
brew install --cask cmux
```
Or [download the DMG](https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg) directly.
Or [download the DMG](https://github.com/manaflow-ai/cmux/releases/latest/download/cmux-macos.dmg) directly.
## Key Features

View file

@ -1,6 +1,6 @@
---
title: Installation
description: How to install cmuxterm on macOS
description: How to install cmux on macOS
---
# Installation
@ -10,29 +10,29 @@ description: How to install cmuxterm on macOS
Install via Homebrew:
```bash
brew tap manaflow-ai/cmuxterm
brew install --cask cmuxterm
brew tap manaflow-ai/cmux
brew install --cask cmux
```
To update later:
```bash
brew upgrade --cask cmuxterm
brew upgrade --cask cmux
```
## Manual Download
Download the latest release from GitHub:
<a href="https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg">
<img src="/macos-badge.png" alt="Download cmuxterm for macOS" width="180" />
<a href="https://github.com/manaflow-ai/cmux/releases/latest/download/cmux-macos.dmg">
<img src="/macos-badge.png" alt="Download cmux for macOS" width="180" />
</a>
Then:
1. Open the downloaded `.dmg` file
2. Drag **cmuxterm** to your **Applications** folder
3. Open cmuxterm from Applications
2. Drag **cmux** to your **Applications** folder
3. Open cmux from Applications
<Callout type="info">
On first launch, macOS may ask you to confirm opening an app from an identified developer. Click **Open** to proceed.
@ -40,7 +40,7 @@ Then:
## Verify Installation
Open cmuxterm and you should see:
Open cmux and you should see:
- A terminal window with a vertical tab sidebar on the left
- One initial tab already open
@ -48,23 +48,23 @@ Open cmuxterm and you should see:
## CLI Tool
cmuxterm includes a command-line tool for automation. It's bundled inside the app and works automatically when you run commands inside cmuxterm.
cmux includes a command-line tool for automation. It's bundled inside the app and works automatically when you run commands inside cmux.
To use the CLI from outside cmuxterm, you can create a symlink:
To use the CLI from outside cmux, you can create a symlink:
```bash
sudo ln -sf "/Applications/cmuxterm.app/Contents/MacOS/cmuxterm" /usr/local/bin/cmuxterm
sudo ln -sf "/Applications/cmux.app/Contents/MacOS/cmux" /usr/local/bin/cmux
```
Then you can run commands like:
```bash
cmuxterm list-tabs
cmuxterm notify --title "Build Complete" --body "Your build finished successfully"
cmux list-tabs
cmux notify --title "Build Complete" --body "Your build finished successfully"
```
## Updates
cmuxterm checks for updates automatically using Sparkle. When an update is available, you'll see an update pill in the titlebar. Click it to install the latest version.
cmux checks for updates automatically using Sparkle. When an update is available, you'll see an update pill in the titlebar. Click it to install the latest version.
You can also check for updates manually via **cmuxterm → Check for Updates** in the menu bar.
You can also check for updates manually via **cmux → Check for Updates** in the menu bar.

View file

@ -1,6 +1,6 @@
---
title: Keyboard Shortcuts
description: Complete list of cmuxterm keyboard shortcuts
description: Complete list of cmux keyboard shortcuts
---
# Keyboard Shortcuts

View file

@ -1,16 +1,16 @@
---
title: Notifications
description: Desktop notifications in cmuxterm for AI agents
description: Desktop notifications in cmux for AI agents
---
# Notifications
cmuxterm supports desktop notifications, allowing AI agents and scripts to alert you when they need attention.
cmux supports desktop notifications, allowing AI agents and scripts to alert you when they need attention.
## How It Works
1. A process in the terminal sends an OSC escape sequence
2. cmuxterm parses the notification title and body
2. cmux parses the notification title and body
3. If the terminal isn't focused, a desktop notification appears
4. The tab shows an unread badge
@ -21,13 +21,13 @@ cmuxterm supports desktop notifications, allowing AI agents and scripts to alert
The simplest way to send a notification:
```bash
cmuxterm notify --title "Task Complete" --body "Your build finished"
cmux notify --title "Task Complete" --body "Your build finished"
```
With a subtitle:
```bash
cmuxterm notify --title "Claude Code" --subtitle "Waiting" --body "Agent needs input"
cmux notify --title "Claude Code" --subtitle "Waiting" --body "Agent needs input"
```
### Using OSC Sequences
@ -59,7 +59,7 @@ See [OSC Sequences](/docs/osc-sequences) for full format documentation.
Notifications are suppressed (no desktop alert) when:
- The cmuxterm window is focused
- The cmux window is focused
- The specific tab sending the notification is active
- The notification panel is open
@ -94,9 +94,9 @@ notify-after() {
"$@"
local exit_code=$?
if [ $exit_code -eq 0 ]; then
cmuxterm notify --title "✓ Command Complete" --body "$1"
cmux notify --title "✓ Command Complete" --body "$1"
else
cmuxterm notify --title "✗ Command Failed" --body "$1 (exit $exit_code)"
cmux notify --title "✗ Command Failed" --body "$1 (exit $exit_code)"
fi
return $exit_code
}
@ -108,7 +108,7 @@ notify-after npm run build
### Notify When Build Finishes
```bash
npm run build && cmuxterm notify --title "Build Success" --body "Ready to deploy"
npm run build && cmux notify --title "Build Success" --body "Ready to deploy"
```
### Watch Script Completion
@ -123,5 +123,5 @@ do_work() {
}
do_work
cmuxterm notify --title "Task Complete" --body "long-running-task.sh finished"
cmux notify --title "Task Complete" --body "long-running-task.sh finished"
```

View file

@ -1,11 +1,11 @@
---
title: OSC Sequences
description: OSC escape sequence reference for cmuxterm notifications
description: OSC escape sequence reference for cmux notifications
---
# OSC Sequences
cmuxterm supports two OSC (Operating System Command) escape sequence standards for sending notifications from terminal programs.
cmux supports two OSC (Operating System Command) escape sequence standards for sending notifications from terminal programs.
## OSC 99 (Kitty Protocol)
@ -137,13 +137,13 @@ notify_osc777 "Download Complete" "File saved to ~/Downloads"
| Subtitle | ✓ | ✗ |
| Notification ID | ✓ | ✗ |
| Complexity | Higher | Lower |
| Compatibility | Kitty, cmuxterm | RXVT, cmuxterm |
| Compatibility | Kitty, cmux | RXVT, cmux |
## Recommendations
- **Use OSC 777** for simple title + body notifications
- **Use OSC 99** when you need subtitles or notification IDs
- **Use the CLI** (`cmuxterm notify`) for the easiest integration
- **Use the CLI** (`cmux notify`) for the easiest integration
## Testing
@ -163,7 +163,7 @@ printf '\e]99;i=test;e=1;d=1;p=body:This is the body text\e\\'
### tmux
If using tmux inside cmuxterm, enable passthrough:
If using tmux inside cmux, enable passthrough:
```bash
# In .tmux.conf

View file

@ -1,24 +1,24 @@
---
title: Socket API
description: Unix socket API for programmatic control of cmuxterm
description: Unix socket API for programmatic control of cmux
---
# Socket API
cmuxterm exposes a Unix socket for programmatic control, enabling automation and integration with tools like Claude Code.
cmux exposes a Unix socket for programmatic control, enabling automation and integration with tools like Claude Code.
## Socket Location
| Build | Socket Path |
|-------|-------------|
| Release | `/tmp/cmuxterm.sock` |
| Debug | `/tmp/cmuxterm-debug.sock` |
| Release | `/tmp/cmux.sock` |
| Debug | `/tmp/cmux-debug.sock` |
You can override the path with the `CMUX_SOCKET_PATH` environment variable.
## Access Modes
cmuxterm has three access modes, configurable in Settings:
cmux has three access modes, configurable in Settings:
| Mode | Description |
|------|-------------|
@ -227,7 +227,7 @@ import json
def send_command(cmd):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect('/tmp/cmuxterm.sock')
sock.connect('/tmp/cmux.sock')
sock.send(json.dumps(cmd).encode() + b'\n')
response = sock.recv(4096).decode()
sock.close()
@ -252,7 +252,7 @@ send_command({
# Function to send commands
cmux_cmd() {
echo "$1" | nc -U /tmp/cmuxterm.sock
echo "$1" | nc -U /tmp/cmux.sock
}
# List tabs

View file

@ -1,11 +1,11 @@
---
title: Split Panes
description: Working with split panes in cmuxterm
description: Working with split panes in cmux
---
# Split Panes
cmuxterm supports splitting terminal panes within a tab, allowing you to view multiple terminals side by side.
cmux supports splitting terminal panes within a tab, allowing you to view multiple terminals side by side.
## Creating Splits
@ -20,10 +20,10 @@ cmuxterm supports splitting terminal panes within a tab, allowing you to view mu
```bash
# Split in a specific direction
cmuxterm new-split right
cmuxterm new-split down
cmuxterm new-split left
cmuxterm new-split up
cmux new-split right
cmux new-split down
cmux new-split left
cmux new-split up
```
## Navigating Splits
@ -43,10 +43,10 @@ Use **⌘⌥** + arrow keys to move between splits:
```bash
# List all panes in current tab
cmuxterm list-panels
cmux list-panels
# Focus a specific pane
cmuxterm focus-panel --panel <panel-id>
cmux focus-panel --panel <panel-id>
```
## Closing Splits
@ -75,7 +75,7 @@ split-divider-color = #45475a
## Split Layout
cmuxterm uses a binary split tree structure:
cmux uses a binary split tree structure:
- Each split creates two child panes
- Nested splits create a tree of panes
@ -104,8 +104,8 @@ With the CLI, you can send input to specific panes:
```bash
# Send text to a specific pane
cmuxterm send-panel --panel <panel-id> "echo hello"
cmux send-panel --panel <panel-id> "echo hello"
# Send a keypress to a specific pane
cmuxterm send-key-panel --panel <panel-id> enter
cmux send-key-panel --panel <panel-id> enter
```

View file

@ -1,11 +1,11 @@
---
title: Vertical Tabs
description: Managing terminal tabs in cmuxterm
description: Managing terminal tabs in cmux
---
# Vertical Tabs
cmuxterm displays all your terminal tabs in a vertical sidebar on the left side of the window, making it easy to see and switch between multiple terminals at a glance.
cmux displays all your terminal tabs in a vertical sidebar on the left side of the window, making it easy to see and switch between multiple terminals at a glance.
## Sidebar Features

View file

@ -1,11 +1,11 @@
{
"name": "cmuxterm-docs",
"name": "cmux-docs",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cmuxterm-docs",
"name": "cmux-docs",
"version": "0.1.0",
"dependencies": {
"fumadocs-core": "^14.0.0",

View file

@ -1,5 +1,5 @@
{
"name": "cmuxterm-docs",
"name": "cmux-docs",
"version": "0.1.0",
"private": true,
"scripts": {

View file

@ -1,29 +1,29 @@
# Notifications
cmuxterm provides a notification panel for AI agents like Claude Code, Codex, and OpenCode. Notifications appear in a dedicated panel and trigger macOS system notifications.
cmux provides a notification panel for AI agents like Claude Code, Codex, and OpenCode. Notifications appear in a dedicated panel and trigger macOS system notifications.
## Quick Start
```bash
# Send a notification (if cmuxterm is available)
command -v cmuxterm &>/dev/null && cmuxterm notify --title "Done" --body "Task complete"
# Send a notification (if cmux is available)
command -v cmux &>/dev/null && cmux notify --title "Done" --body "Task complete"
# With fallback to macOS notifications
command -v cmuxterm &>/dev/null && cmuxterm notify --title "Done" --body "Task complete" || osascript -e 'display notification "Task complete" with title "Done"'
command -v cmux &>/dev/null && cmux notify --title "Done" --body "Task complete" || osascript -e 'display notification "Task complete" with title "Done"'
```
## Detection
Check if `cmuxterm` CLI is available before using it:
Check if `cmux` CLI is available before using it:
```bash
# Shell
if command -v cmuxterm &>/dev/null; then
cmuxterm notify --title "Hello"
if command -v cmux &>/dev/null; then
cmux notify --title "Hello"
fi
# One-liner with fallback
command -v cmuxterm &>/dev/null && cmuxterm notify --title "Hello" || osascript -e 'display notification "" with title "Hello"'
command -v cmux &>/dev/null && cmux notify --title "Hello" || osascript -e 'display notification "" with title "Hello"'
```
```python
@ -32,8 +32,8 @@ import shutil
import subprocess
def notify(title: str, body: str = ""):
if shutil.which("cmuxterm"):
subprocess.run(["cmuxterm", "notify", "--title", title, "--body", body])
if shutil.which("cmux"):
subprocess.run(["cmux", "notify", "--title", title, "--body", body])
else:
# Fallback to macOS
subprocess.run(["osascript", "-e", f'display notification "{body}" with title "{title}"'])
@ -43,13 +43,13 @@ def notify(title: str, body: str = ""):
```bash
# Simple notification
cmuxterm notify --title "Build Complete"
cmux notify --title "Build Complete"
# With subtitle and body
cmuxterm notify --title "Claude Code" --subtitle "Permission" --body "Approval needed"
cmux notify --title "Claude Code" --subtitle "Permission" --body "Approval needed"
# Notify specific tab/panel
cmuxterm notify --title "Done" --tab 0 --panel 1
cmux notify --title "Done" --tab 0 --panel 1
```
## Integration Examples
@ -67,7 +67,7 @@ Add to `~/.claude/settings.json`:
"hooks": [
{
"type": "command",
"command": "command -v cmuxterm &>/dev/null && cmuxterm notify --title 'Claude Code' --body 'Waiting for input' || osascript -e 'display notification \"Waiting for input\" with title \"Claude Code\"'"
"command": "command -v cmux &>/dev/null && cmux notify --title 'Claude Code' --body 'Waiting for input' || osascript -e 'display notification \"Waiting for input\" with title \"Claude Code\"'"
}
]
},
@ -76,7 +76,7 @@ Add to `~/.claude/settings.json`:
"hooks": [
{
"type": "command",
"command": "command -v cmuxterm &>/dev/null && cmuxterm notify --title 'Claude Code' --subtitle 'Permission' --body 'Approval needed' || osascript -e 'display notification \"Approval needed\" with title \"Claude Code\"'"
"command": "command -v cmux &>/dev/null && cmux notify --title 'Claude Code' --subtitle 'Permission' --body 'Approval needed' || osascript -e 'display notification \"Approval needed\" with title \"Claude Code\"'"
}
]
}
@ -90,7 +90,7 @@ Add to `~/.claude/settings.json`:
Add to `~/.codex/config.toml`:
```toml
notify = ["bash", "-c", "command -v cmuxterm &>/dev/null && cmuxterm notify --title Codex --body \"$(echo $1 | jq -r '.\"last-assistant-message\" // \"Turn complete\"' 2>/dev/null | head -c 100)\" || osascript -e 'display notification \"Turn complete\" with title \"Codex\"'", "--"]
notify = ["bash", "-c", "command -v cmux &>/dev/null && cmux notify --title Codex --body \"$(echo $1 | jq -r '.\"last-assistant-message\" // \"Turn complete\"' 2>/dev/null | head -c 100)\" || osascript -e 'display notification \"Turn complete\" with title \"Codex\"'", "--"]
```
Or create a simple script `~/.local/bin/codex-notify.sh`:
@ -98,7 +98,7 @@ Or create a simple script `~/.local/bin/codex-notify.sh`:
```bash
#!/bin/bash
MSG=$(echo "$1" | jq -r '."last-assistant-message" // "Turn complete"' 2>/dev/null | head -c 100)
command -v cmuxterm &>/dev/null && cmuxterm notify --title "Codex" --body "$MSG" || osascript -e "display notification \"$MSG\" with title \"Codex\""
command -v cmux &>/dev/null && cmux notify --title "Codex" --body "$MSG" || osascript -e "display notification \"$MSG\" with title \"Codex\""
```
Then use:
@ -108,13 +108,13 @@ notify = ["bash", "~/.local/bin/codex-notify.sh"]
### OpenCode Plugin
Create `.opencode/plugins/cmuxterm-notify.js`:
Create `.opencode/plugins/cmux-notify.js`:
```javascript
export const CmuxNotificationPlugin = async ({ $, }) => {
const notify = async (title, body) => {
try {
await $`command -v cmuxterm && cmuxterm notify --title ${title} --body ${body}`;
await $`command -v cmux && cmux notify --title ${title} --body ${body}`;
} catch {
await $`osascript -e ${"display notification \"" + body + "\" with title \"" + title + "\""}`;
}
@ -132,7 +132,7 @@ export const CmuxNotificationPlugin = async ({ $, }) => {
## Environment Variables
cmuxterm sets these in child shells:
cmux sets these in child shells:
| Variable | Description |
|----------|-------------|
@ -143,14 +143,14 @@ cmuxterm sets these in child shells:
## CLI Commands
```
cmuxterm notify --title <text> [--subtitle <text>] [--body <text>] [--tab <id|index>] [--panel <id|index>]
cmuxterm list-notifications
cmuxterm clear-notifications
cmuxterm ping
cmux notify --title <text> [--subtitle <text>] [--body <text>] [--tab <id|index>] [--panel <id|index>]
cmux list-notifications
cmux clear-notifications
cmux ping
```
## Best Practices
1. **Always check availability first** - Use `command -v cmuxterm` before calling
1. **Always check availability first** - Use `command -v cmux` before calling
2. **Provide fallbacks** - Use `|| osascript` for macOS fallback
3. **Keep notifications concise** - Title should be brief, use body for details

1
homebrew-cmux Submodule

@ -0,0 +1 @@
Subproject commit 19af91f6d47016a74a08546e8693dc03eefb7ef5

@ -1 +0,0 @@
Subproject commit c12d753824b3ab1f4289a7e45d7ea28aa114b496

View file

@ -1,9 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="cmuxterm DEV"
BUNDLE_ID="com.cmuxterm.app.debug"
BASE_APP_NAME="cmuxterm DEV"
APP_NAME="cmux DEV"
BUNDLE_ID="com.cmux.app.debug"
BASE_APP_NAME="cmux DEV"
DERIVED_DATA=""
NAME_SET=0
BUNDLE_SET=0
@ -97,13 +97,13 @@ if [[ -n "$TAG" ]]; then
TAG_ID="$(sanitize_bundle "$TAG")"
TAG_SLUG="$(sanitize_path "$TAG")"
if [[ "$NAME_SET" -eq 0 ]]; then
APP_NAME="cmuxterm DEV ${TAG}"
APP_NAME="cmux DEV ${TAG}"
fi
if [[ "$BUNDLE_SET" -eq 0 ]]; then
BUNDLE_ID="com.cmuxterm.app.debug.${TAG_ID}"
BUNDLE_ID="com.cmux.app.debug.${TAG_ID}"
fi
if [[ "$DERIVED_SET" -eq 0 ]]; then
DERIVED_DATA="/tmp/cmuxterm-${TAG_SLUG}"
DERIVED_DATA="/tmp/cmux-${TAG_SLUG}"
fi
fi
@ -180,10 +180,10 @@ if [[ -n "$TAG" && "$APP_NAME" != "$SEARCH_APP_NAME" ]]; then
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$INFO_PLIST" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIdentifier string $BUNDLE_ID" "$INFO_PLIST"
if [[ -n "${TAG_SLUG:-}" ]]; then
APP_SUPPORT_DIR="$HOME/Library/Application Support/cmuxterm"
APP_SUPPORT_DIR="$HOME/Library/Application Support/cmux"
CMUXD_SOCKET="${APP_SUPPORT_DIR}/cmuxd-dev-${TAG_SLUG}.sock"
CMUX_SOCKET="/tmp/cmuxterm-debug-${TAG_SLUG}.sock"
echo "$CMUX_SOCKET" > /tmp/cmuxterm-last-socket-path || true
CMUX_SOCKET="/tmp/cmux-debug-${TAG_SLUG}.sock"
echo "$CMUX_SOCKET" > /tmp/cmux-last-socket-path || true
/usr/libexec/PlistBuddy -c "Add :LSEnvironment dict" "$INFO_PLIST" 2>/dev/null || true
/usr/libexec/PlistBuddy -c "Set :LSEnvironment:CMUXD_UNIX_PATH \"${CMUXD_SOCKET}\"" "$INFO_PLIST" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :LSEnvironment:CMUXD_UNIX_PATH string \"${CMUXD_SOCKET}\"" "$INFO_PLIST"
@ -225,8 +225,8 @@ if [[ -x "$CMUXD_SRC" ]]; then
cp "$CMUXD_SRC" "$BIN_DIR/cmuxd"
chmod +x "$BIN_DIR/cmuxd"
fi
# Avoid inheriting cmuxterm/ghostty environment variables from the terminal that
# runs this script (often inside another cmuxterm instance), which can cause
# Avoid inheriting cmux/ghostty environment variables from the terminal that
# runs this script (often inside another cmux instance), which can cause
# socket and resource-path conflicts.
OPEN_CLEAN_ENV=(
env
@ -235,15 +235,13 @@ OPEN_CLEAN_ENV=(
-u CMUX_PANEL_ID
-u CMUXD_UNIX_PATH
-u CMUX_TAG
-u CMUXTERM_TAG
-u CMUX_BUNDLE_ID
-u CMUXTERM_BUNDLE_ID
-u CMUX_SHELL_INTEGRATION
-u GHOSTTY_BIN_DIR
-u GHOSTTY_RESOURCES_DIR
-u GHOSTTY_SHELL_FEATURES
# Dev shells (including CI/Codex) often force-disable paging by exporting these.
# Don't leak that into cmuxterm, otherwise `git diff` won't page even with PAGER=less.
# Don't leak that into cmux, otherwise `git diff` won't page even with PAGER=less.
-u GIT_PAGER
-u GH_PAGER
-u TERMINFO

View file

@ -2,20 +2,20 @@
set -euo pipefail
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Release -destination 'platform=macOS' build
pkill -x cmuxterm || true
pkill -x cmux || true
sleep 0.2
APP_PATH="$(
find "$HOME/Library/Developer/Xcode/DerivedData" -path "*/Build/Products/Release/cmuxterm.app" -print0 \
find "$HOME/Library/Developer/Xcode/DerivedData" -path "*/Build/Products/Release/cmux.app" -print0 \
| xargs -0 /usr/bin/stat -f "%m %N" 2>/dev/null \
| sort -nr \
| head -n 1 \
| cut -d' ' -f2-
)"
if [[ -z "${APP_PATH}" ]]; then
echo "cmuxterm.app not found in DerivedData" >&2
echo "cmux.app not found in DerivedData" >&2
exit 1
fi
# Dev shells (including CI/Codex) often force-disable paging by exporting these.
# Don't leak that into cmuxterm, otherwise `git diff` won't page even with PAGER=less.
# Don't leak that into cmux, otherwise `git diff` won't page even with PAGER=less.
env -u GIT_PAGER -u GH_PAGER open "$APP_PATH"
osascript -e 'tell application "cmuxterm" to activate' || true
osascript -e 'tell application "cmux" to activate' || true

View file

@ -16,8 +16,8 @@ if [[ -z "${SPARKLE_PRIVATE_KEY:-}" ]]; then
fi
SPARKLE_VERSION="${SPARKLE_VERSION:-2.8.1}"
DOWNLOAD_URL_PREFIX="${DOWNLOAD_URL_PREFIX:-https://github.com/manaflow-ai/cmuxterm/releases/download/$TAG/}"
RELEASE_NOTES_URL="${RELEASE_NOTES_URL:-https://github.com/manaflow-ai/cmuxterm/releases/tag/$TAG}"
DOWNLOAD_URL_PREFIX="${DOWNLOAD_URL_PREFIX:-https://github.com/manaflow-ai/cmux/releases/download/$TAG/}"
RELEASE_NOTES_URL="${RELEASE_NOTES_URL:-https://github.com/manaflow-ai/cmux/releases/tag/$TAG}"
work_dir="$(mktemp -d)"
cleanup() {

View file

@ -2,7 +2,7 @@
set -euo pipefail
SPARKLE_VERSION="${SPARKLE_VERSION:-2.8.1}"
SPARKLE_KEYCHAIN_ACCOUNT="${SPARKLE_KEYCHAIN_ACCOUNT:-cmuxterm}"
SPARKLE_KEYCHAIN_ACCOUNT="${SPARKLE_KEYCHAIN_ACCOUNT:-cmux}"
SPARKLE_ENV_FILE="${SPARKLE_ENV_FILE:-.env}"
work_dir="$(mktemp -d)"

View file

@ -43,8 +43,8 @@ class cmuxError(Exception):
pass
_LAST_SOCKET_PATH_FILE = "/tmp/cmuxterm-last-socket-path"
_DEFAULT_DEBUG_BUNDLE_ID = "com.cmuxterm.app.debug"
_LAST_SOCKET_PATH_FILE = "/tmp/cmux-last-socket-path"
_DEFAULT_DEBUG_BUNDLE_ID = "com.cmux.app.debug"
def _sanitize_tag_slug(raw: str) -> str:
@ -68,11 +68,11 @@ def _quote_option_value(value: str) -> str:
def _default_bundle_id() -> str:
override = os.environ.get("CMUX_BUNDLE_ID") or os.environ.get("CMUXTERM_BUNDLE_ID")
override = os.environ.get("CMUX_BUNDLE_ID")
if override:
return override
tag = os.environ.get("CMUX_TAG") or os.environ.get("CMUXTERM_TAG")
tag = os.environ.get("CMUX_TAG")
if tag:
suffix = _sanitize_bundle_suffix(tag)
return f"{_DEFAULT_DEBUG_BUNDLE_ID}.{suffix}"
@ -110,12 +110,12 @@ def _can_connect(path: str, timeout: float = 0.15, retries: int = 4) -> bool:
def _default_socket_path() -> str:
tag = os.environ.get("CMUX_TAG") or os.environ.get("CMUXTERM_TAG")
tag = os.environ.get("CMUX_TAG")
if tag:
slug = _sanitize_tag_slug(tag)
tagged_candidates = [
f"/tmp/cmuxterm-debug-{slug}.sock",
f"/tmp/cmuxterm-{slug}.sock",
f"/tmp/cmux-debug-{slug}.sock",
f"/tmp/cmux-{slug}.sock",
]
for path in tagged_candidates:
if os.path.exists(path) and _can_connect(path):
@ -142,13 +142,13 @@ def _default_socket_path() -> str:
return last_socket
# Prefer the non-tagged sockets when present.
candidates = ["/tmp/cmuxterm-debug.sock", "/tmp/cmuxterm.sock"]
candidates = ["/tmp/cmux-debug.sock", "/tmp/cmux.sock"]
for path in candidates:
if os.path.exists(path) and _can_connect(path):
return path
# Otherwise, fall back to the newest tagged debug socket if there is one.
tagged = glob.glob("/tmp/cmuxterm-debug-*.sock")
tagged = glob.glob("/tmp/cmux-debug-*.sock")
tagged = [p for p in tagged if os.path.exists(p)]
if tagged:
tagged.sort(key=lambda p: os.path.getmtime(p), reverse=True)

View file

@ -1,24 +1,24 @@
#!/bin/bash
# Test script that sends keystrokes to cmuxterm via AppleScript
# Test script that sends keystrokes to cmux via AppleScript
# This tests the actual keyboard input path through the app
set -e
echo "=== cmuxterm Keystroke Test ==="
echo "=== cmux Keystroke Test ==="
echo ""
# Check if cmuxterm is running
if ! pgrep -x "cmuxterm" > /dev/null; then
echo "Error: cmuxterm is not running"
echo "Please start cmuxterm first"
# Check if cmux is running
if ! pgrep -x "cmux" > /dev/null; then
echo "Error: cmux is not running"
echo "Please start cmux first"
exit 1
fi
echo "cmuxterm is running"
echo "cmux is running"
echo ""
# Activate cmuxterm
osascript -e 'tell application "cmuxterm" to activate'
# Activate cmux
osascript -e 'tell application "cmux" to activate'
sleep 0.5
echo "Test 1: Testing Ctrl+C (SIGINT)"
@ -56,7 +56,7 @@ echo " If cat exited, Ctrl+D is working!"
echo ""
echo "=== Manual Verification Required ==="
echo "Please check the cmuxterm window to verify:"
echo "Please check the cmux window to verify:"
echo " 1. The 'sleep 30' command was interrupted by Ctrl+C"
echo " 2. The 'cat' command exited after Ctrl+D"
echo ""

View file

@ -10,7 +10,7 @@ Tests that CPU usage stays reasonable when:
Usage:
python3 tests/test_cpu_notifications.py
Requires cmuxterm to be running with socket control enabled.
Requires cmux to be running with socket control enabled.
"""
from __future__ import annotations
@ -39,8 +39,8 @@ SETTLE_TIME = 2.0
MONITOR_DURATION = 3.0
def get_cmuxterm_pid() -> Optional[int]:
"""Get the PID of the running cmuxterm process."""
def get_cmux_pid() -> Optional[int]:
"""Get the PID of the running cmux process."""
socket_path = os.environ.get("CMUX_SOCKET_PATH")
if not socket_path:
# Ask cmux.py to resolve default socket path (supports CMUX_TAG and last-socket file).
@ -68,14 +68,14 @@ def get_cmuxterm_pid() -> Optional[int]:
return pid
result = subprocess.run(
["pgrep", "-f", r"cmuxterm\.app/Contents/MacOS/cmuxterm$"],
["pgrep", "-f", r"cmux\.app/Contents/MacOS/cmux$"],
capture_output=True,
text=True,
)
if result.returncode != 0:
# Try DEV build
result = subprocess.run(
["pgrep", "-f", r"cmuxterm DEV\.app/Contents/MacOS/cmuxterm"],
["pgrep", "-f", r"cmux DEV\.app/Contents/MacOS/cmux"],
capture_output=True,
text=True,
)
@ -167,7 +167,7 @@ def test_cpu_after_popover_close(client: cmux, pid: int) -> tuple[bool, str]:
time.sleep(0.1)
time.sleep(0.5)
# Ensure the correct cmuxterm instance is frontmost (tag-safe).
# Ensure the correct cmux instance is frontmost (tag-safe).
bundle_id = cmux.default_bundle_id()
subprocess.run(
["osascript", "-e", f'tell application id "{bundle_id}" to activate'],
@ -243,17 +243,17 @@ def test_cpu_idle_with_notifications(client: cmux, pid: int) -> tuple[bool, str]
def main():
print("=" * 60)
print("cmuxterm Notification CPU Tests")
print("cmux Notification CPU Tests")
print("=" * 60)
socket_path = cmux().socket_path
pid = get_cmuxterm_pid()
pid = get_cmux_pid()
if pid is None:
print("\n❌ SKIP: cmuxterm is not running")
print("\n❌ SKIP: cmux is not running")
return 0
print(f"\nFound cmuxterm process: PID {pid}")
print(f"\nFound cmux process: PID {pid}")
# Try to connect to the socket
client = cmux(socket_path)
@ -261,7 +261,7 @@ def main():
client.connect()
print(f"Connected to {socket_path}")
except cmuxError:
print("\n❌ SKIP: Could not connect to cmuxterm socket")
print("\n❌ SKIP: Could not connect to cmux socket")
print("Tip: set CMUX_TAG=<tag> or CMUX_SOCKET_PATH=<path> to target a tagged instance.")
return 0

View file

@ -1,11 +1,11 @@
#!/usr/bin/env python3
"""
CPU usage test for cmuxterm.
CPU usage test for cmux.
This test monitors cmuxterm's CPU usage during idle periods to catch
This test monitors cmux's CPU usage during idle periods to catch
performance regressions like runaway animations or continuous view updates.
Run this test after launching cmuxterm:
Run this test after launching cmux:
python3 tests/test_cpu_usage.py
The test will fail if:
@ -49,8 +49,8 @@ SUSPICIOUS_PATTERNS = [
]
def get_cmuxterm_pid() -> Optional[int]:
"""Get the PID of the running cmuxterm process."""
def get_cmux_pid() -> Optional[int]:
"""Get the PID of the running cmux process."""
socket_path = os.environ.get("CMUX_SOCKET_PATH")
if not socket_path:
try:
@ -77,14 +77,14 @@ def get_cmuxterm_pid() -> Optional[int]:
return pid
result = subprocess.run(
["pgrep", "-f", r"cmuxterm\.app/Contents/MacOS/cmuxterm$"],
["pgrep", "-f", r"cmux\.app/Contents/MacOS/cmux$"],
capture_output=True,
text=True,
)
if result.returncode != 0:
# Try DEV build
result = subprocess.run(
["pgrep", "-f", r"cmuxterm DEV\.app/Contents/MacOS/cmuxterm"],
["pgrep", "-f", r"cmux DEV\.app/Contents/MacOS/cmux"],
capture_output=True,
text=True,
)
@ -141,17 +141,17 @@ def monitor_cpu_usage(pid: int, duration: float, interval: float) -> List[float]
def main():
print("=" * 60)
print("cmuxterm CPU Usage Test")
print("cmux CPU Usage Test")
print("=" * 60)
# Find cmuxterm process
pid = get_cmuxterm_pid()
# Find cmux process
pid = get_cmux_pid()
if pid is None:
print("\n❌ SKIP: cmuxterm is not running")
print("Start cmuxterm and run this test again.")
print("\n❌ SKIP: cmux is not running")
print("Start cmux and run this test again.")
return 0 # Not a failure, just skip
print(f"\nFound cmuxterm process: PID {pid}")
print(f"\nFound cmux process: PID {pid}")
# Wait for app to settle
print(f"Waiting {SETTLE_TIME}s for app to settle...")
@ -187,7 +187,7 @@ def main():
print(f" - {issue}")
# Save sample for debugging
sample_file = Path(f"/tmp/cmuxterm_cpu_test_sample_{pid}.txt")
sample_file = Path(f"/tmp/cmux_cpu_test_sample_{pid}.txt")
sample_file.write_text(sample_output)
print(f"\nFull sample saved to: {sample_file}")
@ -196,7 +196,7 @@ def main():
lines = sample_output.split("\n")
relevant_lines = [
l for l in lines
if "cmuxterm" in l and ("body" in l or "Animation" in l or "Timer" in l)
if "cmux" in l and ("body" in l or "Animation" in l or "Timer" in l)
][:10]
for line in relevant_lines:
print(f" {line.strip()[:100]}")

View file

@ -3,7 +3,7 @@
Automated test for ctrl+enter keybind using real keystrokes.
Requires:
- cmuxterm running
- cmux running
- Accessibility permissions for System Events (osascript)
- keybind = ctrl+enter=text:\\r (or \\n/\\x0d) configured in Ghostty config
"""
@ -115,14 +115,14 @@ def test_ctrl_enter_keybind(client: cmux) -> tuple[bool, str]:
def run_tests() -> int:
print("=" * 60)
print("cmuxterm Ctrl+Enter Keybind Test")
print("cmux Ctrl+Enter Keybind Test")
print("=" * 60)
print()
socket_path = cmux.default_socket_path()
if not os.path.exists(socket_path):
print(f"SKIP: Socket not found at {socket_path}")
print("Tip: start cmuxterm first (or set CMUX_TAG / CMUX_SOCKET_PATH).")
print("Tip: start cmux first (or set CMUX_TAG / CMUX_SOCKET_PATH).")
return 0
config_path = find_config_with_keybind()

View file

@ -4,8 +4,8 @@ Regression: GhosttyKit already injects zsh shell integration by setting ZDOTDIR
to Ghostty's own integration directory (and optionally preserving a user-set
ZDOTDIR in GHOSTTY_ZSH_ZDOTDIR).
cmuxterm also injects its own zsh integration by setting ZDOTDIR to
Resources/shell-integration. If cmuxterm incorrectly treats Ghostty's injected
cmux also injects its own zsh integration by setting ZDOTDIR to
Resources/shell-integration. If cmux incorrectly treats Ghostty's injected
ZDOTDIR as the "user" ZDOTDIR, zsh history will be isolated to the integration
directory rather than the user's HOME/ZDOTDIR, breaking cross-terminal history
and therefore zsh-autosuggestions.
@ -63,7 +63,7 @@ def main() -> int:
env.pop("GHOSTTY_SHELL_FEATURES", None)
env.pop("GHOSTTY_BIN_DIR", None)
# Simulate the buggy situation: cmuxterm stores Ghostty's injected ZDOTDIR
# Simulate the buggy situation: cmux stores Ghostty's injected ZDOTDIR
# as the "original" ZDOTDIR, then sets ZDOTDIR to its own wrapper.
env["CMUX_ORIGINAL_ZDOTDIR"] = str(ghostty_zsh_dir)
env["ZDOTDIR"] = str(cmux_wrapper_dir)

View file

@ -3,8 +3,8 @@
Regression: if the user's .zshenv changes ZDOTDIR, then .zshrc should be sourced
from the updated ZDOTDIR (matching vanilla zsh semantics).
Why this matters for cmuxterm:
- cmuxterm sets ZDOTDIR to the app wrapper directory so zsh loads wrapper
Why this matters for cmux:
- cmux sets ZDOTDIR to the app wrapper directory so zsh loads wrapper
startup files.
- The wrapper .zshenv temporarily restores ZDOTDIR to the original directory
while sourcing the user's real .zshenv.

View file

@ -3,7 +3,7 @@
Regression: zsh wrapper startup files must source user files with the *original*
ZDOTDIR, not the wrapper directory.
The cmuxterm zsh integration sets ZDOTDIR to the app's wrapper directory so zsh
The cmux zsh integration sets ZDOTDIR to the app's wrapper directory so zsh
loads wrapper .zshenv/.zprofile/.zshrc. Those wrappers must temporarily restore
ZDOTDIR while sourcing the user's real startup files so $ZDOTDIR semantics match
normal zsh behavior.

View file

@ -89,8 +89,8 @@ def _git(cwd: Path, *args: str) -> None:
def _init_git_repo(repo: Path) -> None:
repo.mkdir(parents=True, exist_ok=True)
_git(repo, "init")
_git(repo, "config", "user.email", "cmuxterm-test@example.com")
_git(repo, "config", "user.name", "cmuxterm-test")
_git(repo, "config", "user.email", "cmux-test@example.com")
_git(repo, "config", "user.name", "cmux-test")
(repo / "README.md").write_text("hello\n", encoding="utf-8")
_git(repo, "add", "README.md")
_git(repo, "commit", "-m", "init")
@ -103,7 +103,7 @@ def _init_git_repo(repo: Path) -> None:
def main() -> int:
tag = os.environ.get("CMUX_TAG") or os.environ.get("CMUXTERM_TAG") or ""
tag = os.environ.get("CMUX_TAG") or ""
if not tag:
print("Tip: set CMUX_TAG=<tag> when running this test to avoid socket conflicts.")

View file

@ -83,7 +83,7 @@ def _find_free_allowed_port() -> int:
def _start_external_server(base: Path, port: int) -> subprocess.Popen:
"""
Start an http.server outside cmuxterm and ensure it is actually listening.
Start an http.server outside cmux and ensure it is actually listening.
Retries are handled by the caller by picking a different port.
"""
proc = subprocess.Popen(
@ -201,7 +201,7 @@ def _wait_for_lsof_listen_gone(port: int, timeout: float = 8.0) -> None:
def main() -> int:
tag = os.environ.get("CMUX_TAG") or os.environ.get("CMUXTERM_TAG") or ""
tag = os.environ.get("CMUX_TAG") or ""
if not tag:
print("Tip: set CMUX_TAG=<tag> when running this test to avoid socket conflicts.")
@ -215,7 +215,7 @@ def main() -> int:
shutil.rmtree(base)
base.mkdir(parents=True, exist_ok=True)
# Start a listening server outside cmuxterm. A fresh tab should NOT show this port,
# Start a listening server outside cmux. A fresh tab should NOT show this port,
# since ports should be attributed to the shell session in the tab.
port = None
last_start_err: Exception | None = None

View file

@ -1,12 +1,12 @@
#!/usr/bin/env python3
"""
Regression: cmuxterm relies on Ghostty's xterm-ghostty terminfo entry.
Regression: cmux relies on Ghostty's xterm-ghostty terminfo entry.
In cmuxterm (embedded GhosttyKit), "bright" SGR 90-97 can render incorrectly
In cmux (embedded GhosttyKit), "bright" SGR 90-97 can render incorrectly
for some palettes. zsh-autosuggestions defaults to `fg=8`, which historically
resolved to SGR 90 via terminfo `setaf`.
cmuxterm ships a terminfo overlay that forces bright colors to use indexed
cmux ships a terminfo overlay that forces bright colors to use indexed
256-color sequences (`38;5;<n>` / `48;5;<n>`) instead of SGR 90-97/100-107.
This test ensures the overlay remains in place.
"""