Skip to content

Instantly share code, notes, and snippets.

@YukiMatsumura
Last active March 13, 2026 09:32
Show Gist options
  • Select an option

  • Save YukiMatsumura/410c35d6a3c5a6887e915ec8af04f1ad to your computer and use it in GitHub Desktop.

Select an option

Save YukiMatsumura/410c35d6a3c5a6887e915ec8af04f1ad to your computer and use it in GitHub Desktop.
difit-cmux
#!/usr/bin/env bash
# =============================================================================
# difit-auto-detect.sh
# Automatically detects git state and opens appropriate diff view
# with difit in a cmux browser split pane.
#
# Dependencies:
# - cmux https://cmux.app/
# - difit https://github.com/yoshiko-pg/difit
#
# Setup:
# 1. Install cmux
# Download from https://cmux.app/ and move to /Applications/
#
# 2. Install difit
# npm install -g difit
#
# 3. Make this script executable and place it somewhere on your PATH
# chmod +x difit-auto-detect.sh
# # Example: mv difit-auto-detect.sh /usr/local/bin/difit-cmux
#
# 4. (Optional) Bind to a global shortcut via your preferred launcher
# e.g. Alfred, cmux shortcuts, Raycast, etc.
#
# Usage:
# Run this script while cmux is focused on a git repository pane.
# The script will:
# - Detect the current branch and default branch
# - Show diff against merge-base (for feature branches)
# - Show uncommitted diff (for default branch or detached HEAD)
# - Open the result in a cmux browser split at http://127.0.0.1:<port>/
#
# Optional env vars:
# - DIFIT_PORT: preferred starting port (default: 4966)
# - DIFIT_CLEAN=1: pass --clean to difit
# - DIFIT_TTL_SEC: keep difit alive for N seconds, then auto-stop (default: 1800)
# - DIFIT_EXIT_DELAY_SEC: seconds before stop when TTL is not set (default: 2)
# =============================================================================
set -euo pipefail
CMUX="/Applications/cmux.app/Contents/Resources/bin/cmux"
PREFERRED_PORT="${DIFIT_PORT:-4966}"
TTL_SEC="${DIFIT_TTL_SEC:-1800}"
EXIT_DELAY_SEC="${DIFIT_EXIT_DELAY_SEC:-2}"
find_available_port() {
local port="$1"
while lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; do
port=$((port + 1))
done
echo "$port"
}
# ---------------------------------------------------------------------------
# Get the working directory of the currently focused cmux pane
# ---------------------------------------------------------------------------
targetDir=$("$CMUX" sidebar-state 2>/dev/null | awk -F= '/^focused_cwd=/{print $2}' || true)
if [[ -z "$targetDir" ]]; then
echo " Note: cmux sidebar-state unavailable; using current directory"
targetDir="$(pwd)"
fi
cd "$targetDir" || exit 1
echo "Start Difit in cmux for git repository at: $targetDir"
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "Error: $targetDir is not a git repository" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# Detect current branch and default remote branch
# ---------------------------------------------------------------------------
currentBranch=$(git rev-parse --abbrev-ref HEAD)
defaultBranch=""
# Try to resolve origin/HEAD first, then fall back to origin/main or origin/master
if git remote get-url origin >/dev/null 2>&1; then
defaultBranch=$(git symbolic-ref refs/remotes/origin/HEAD --short 2>/dev/null || true)
if [[ -z "$defaultBranch" ]]; then
if git rev-parse --verify origin/main >/dev/null 2>&1; then
defaultBranch="origin/main"
elif git rev-parse --verify origin/master >/dev/null 2>&1; then
defaultBranch="origin/master"
fi
fi
else
echo " Note: origin remote is not configured; fallback to uncommitted diff mode"
fi
echo " Current branch: $currentBranch (default branch: ${defaultBranch:-none})"
# ---------------------------------------------------------------------------
# Ensure the difit server process is cleaned up on exit
# ---------------------------------------------------------------------------
DIFIT_PID=""
cleanup() {
if [[ -n "$DIFIT_PID" ]]; then
echo "Stopping difit server..."
kill "$DIFIT_PID" 2>/dev/null || true
fi
}
trap cleanup EXIT INT TERM
# ---------------------------------------------------------------------------
# Start difit in background, choosing the right diff range
# ---------------------------------------------------------------------------
echo "Starting difit server..."
port=$(find_available_port "$PREFERRED_PORT")
echo " Port: $port"
difitArgs=(--mode unified --no-open --port "$port" --include-untracked)
if [[ "${DIFIT_CLEAN:-0}" == "1" ]]; then
echo " Option: --clean enabled via DIFIT_CLEAN=1"
difitArgs+=(--clean)
fi
if [[ -z "$defaultBranch" ]] || [[ "$currentBranch" == "${defaultBranch#origin/}" ]]; then
# On default branch, detached HEAD, or no remote found
echo " Mode: uncommitted diff (default branch, detached HEAD, or no remote)"
difit . "${difitArgs[@]}" &
else
# On a feature branch — diff from the merge-base with the default branch
mergeBase=$(git merge-base HEAD "$defaultBranch" 2>/dev/null || true)
if [[ -z "$mergeBase" ]]; then
echo " Mode: could not find merge-base; falling back to uncommitted diff"
difit . "${difitArgs[@]}" &
else
echo " Mode: branch diff from merge-base ($currentBranch vs ${defaultBranch#origin/})"
difit . "$mergeBase" "${difitArgs[@]}" &
fi
fi
DIFIT_PID=$!
# Poll until the difit HTTP server is ready
echo " Waiting for difit server to be ready..."
for _ in {1..120}; do
if curl -fsS "http://localhost:${port}/" > /dev/null 2>&1; then
break
fi
sleep 0.5
done
if ! curl -fsS "http://localhost:${port}/" > /dev/null 2>&1; then
echo "Error: difit server did not become ready within timeout" >&2
exit 1
fi
# Open the diff in a cmux browser pane, falling back to default browser
open_in_cmux() {
local ws
ws=$("$CMUX" identify --no-caller 2>/dev/null | grep workspace_ref | head -1 | sed 's/.*: "//;s/".*//')
if [[ -n "$ws" ]]; then
"$CMUX" new-pane --type browser --workspace "$ws" --url "http://localhost:${port}/" 2>/dev/null
else
return 1
fi
}
if ! open_in_cmux; then
echo " Note: cmux browser pane unavailable; opening in default browser"
open "http://localhost:${port}/"
fi
if [[ -n "$TTL_SEC" ]]; then
if [[ ! "$TTL_SEC" =~ ^[0-9]+$ ]]; then
echo "Error: DIFIT_TTL_SEC must be a non-negative integer" >&2
exit 1
fi
echo " Auto-stop: keep alive for ${TTL_SEC}s"
# Detach auto-stop so this script can exit without killing difit immediately.
nohup sh -c "sleep '$TTL_SEC'; kill '$DIFIT_PID' 2>/dev/null || true" >/dev/null 2>&1 &
DIFIT_PID=""
else
if [[ ! "$EXIT_DELAY_SEC" =~ ^[0-9]+$ ]]; then
echo "Error: DIFIT_EXIT_DELAY_SEC must be a non-negative integer" >&2
exit 1
fi
# Give the browser enough time to load the page before shutting down the server.
sleep "$EXIT_DELAY_SEC"
kill "$DIFIT_PID" 2>/dev/null || true
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment