Instead of creating a merged config directory and injecting CLAUDE_CONFIG_DIR on every terminal spawn, place a thin wrapper script at Resources/bin/claude that intercepts claude invocations to inject --session-id and --settings flags. This eliminates blocking I/O on terminal creation and removes config management complexity. - Add Resources/bin/claude wrapper script with hook injection - Add shell integration PATH fix (re-prepend after .zshrc/.bashrc) - Add transcript reading for richer stop notifications - Add set_status/clear_status to notifications socket allowlist - Add Settings toggle to disable Claude Code integration - Update docs to reflect automatic integration approach - Unset CLAUDECODE env var to avoid nested session detection
172 lines
6.3 KiB
Bash
172 lines
6.3 KiB
Bash
# cmux shell integration for bash
|
|
|
|
_cmux_send() {
|
|
local payload="$1"
|
|
if command -v ncat >/dev/null 2>&1; then
|
|
printf '%s\n' "$payload" | ncat -U "$CMUX_SOCKET_PATH" --send-only
|
|
elif command -v socat >/dev/null 2>&1; then
|
|
printf '%s\n' "$payload" | socat - "UNIX-CONNECT:$CMUX_SOCKET_PATH"
|
|
elif command -v nc >/dev/null 2>&1; then
|
|
# 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. 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
|
|
# short timeout so we never block sidebar updates.
|
|
if printf '%s\n' "$payload" | nc -N -U "$CMUX_SOCKET_PATH" >/dev/null 2>&1; then
|
|
:
|
|
else
|
|
printf '%s\n' "$payload" | nc -w 1 -U "$CMUX_SOCKET_PATH" >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Throttle heavy work to avoid prompt latency.
|
|
_CMUX_PWD_LAST_PWD="${_CMUX_PWD_LAST_PWD:-}"
|
|
_CMUX_GIT_LAST_PWD="${_CMUX_GIT_LAST_PWD:-}"
|
|
_CMUX_GIT_LAST_RUN="${_CMUX_GIT_LAST_RUN:-0}"
|
|
_CMUX_GIT_JOB_PID="${_CMUX_GIT_JOB_PID:-}"
|
|
|
|
_CMUX_PORTS_LAST_RUN="${_CMUX_PORTS_LAST_RUN:-0}"
|
|
_CMUX_PORTS_JOB_PID="${_CMUX_PORTS_JOB_PID:-}"
|
|
|
|
_cmux_prompt_command() {
|
|
[[ -S "$CMUX_SOCKET_PATH" ]] || return 0
|
|
[[ -n "$CMUX_TAB_ID" ]] || return 0
|
|
[[ -n "$CMUX_PANEL_ID" ]] || return 0
|
|
|
|
local now=$SECONDS
|
|
local pwd="$PWD"
|
|
local tty_name=""
|
|
tty_name="$(tty 2>/dev/null || true)"
|
|
tty_name="${tty_name##*/}"
|
|
if [[ "$tty_name" == "not a tty" ]]; then
|
|
tty_name=""
|
|
fi
|
|
|
|
# CWD: keep the app in sync with the actual shell directory.
|
|
if [[ "$pwd" != "$_CMUX_PWD_LAST_PWD" ]]; then
|
|
_CMUX_PWD_LAST_PWD="$pwd"
|
|
{
|
|
local qpwd="${pwd//\"/\\\"}"
|
|
_cmux_send "report_pwd \"${qpwd}\" --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
|
} >/dev/null 2>&1 &
|
|
fi
|
|
|
|
# Git branch/dirty can change without a directory change (e.g. `git checkout`),
|
|
# so update on every prompt (still async + de-duped by the running-job check).
|
|
local should_git=1
|
|
|
|
if (( should_git )); then
|
|
if [[ -n "$_CMUX_GIT_JOB_PID" ]] && kill -0 "$_CMUX_GIT_JOB_PID" 2>/dev/null; then
|
|
:
|
|
else
|
|
_CMUX_GIT_LAST_PWD="$pwd"
|
|
_CMUX_GIT_LAST_RUN=$now
|
|
{
|
|
local branch dirty_opt=""
|
|
branch=$(git branch --show-current 2>/dev/null)
|
|
if [[ -n "$branch" ]]; then
|
|
local first
|
|
first=$(git status --porcelain -uno 2>/dev/null | head -1)
|
|
[[ -n "$first" ]] && dirty_opt="--status=dirty"
|
|
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID"
|
|
else
|
|
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID"
|
|
fi
|
|
} >/dev/null 2>&1 &
|
|
_CMUX_GIT_JOB_PID=$!
|
|
fi
|
|
fi
|
|
|
|
if (( now - _CMUX_PORTS_LAST_RUN >= 10 )); then
|
|
if [[ -n "$_CMUX_PORTS_JOB_PID" ]] && kill -0 "$_CMUX_PORTS_JOB_PID" 2>/dev/null; then
|
|
: # previous scan still running
|
|
else
|
|
_CMUX_PORTS_LAST_RUN=$now
|
|
{
|
|
local ports=()
|
|
local pids_csv=""
|
|
if [[ -n "$tty_name" ]]; then
|
|
pids_csv="$(ps -axo pid=,tty= 2>/dev/null | awk -v tty="$tty_name" '$2 == tty {print $1}' | tr '\n' ',' || true)"
|
|
pids_csv="${pids_csv%,}"
|
|
fi
|
|
|
|
if [[ -n "$pids_csv" ]]; then
|
|
local line name port
|
|
while IFS= read -r line; do
|
|
[[ "$line" == n* ]] || continue
|
|
name="${line#n}"
|
|
name="${name%%->*}"
|
|
port="${name##*:}"
|
|
port="${port%%[^0-9]*}"
|
|
[[ -n "$port" ]] && ports+=("$port")
|
|
done < <(
|
|
lsof -nP -a -p "$pids_csv" -iTCP -sTCP:LISTEN -F n 2>/dev/null || true
|
|
)
|
|
fi
|
|
|
|
if ((${#ports[@]} > 0)); then
|
|
local ports_sorted
|
|
ports_sorted=$(printf '%s\n' "${ports[@]}" | sort -n | uniq | tr '\n' ' ')
|
|
ports_sorted="${ports_sorted%% }"
|
|
_cmux_send "report_ports $ports_sorted --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
|
else
|
|
_cmux_send "clear_ports --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
|
fi
|
|
} >/dev/null 2>&1 &
|
|
_CMUX_PORTS_JOB_PID=$!
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_cmux_install_prompt_command() {
|
|
[[ -n "${_CMUX_PROMPT_INSTALLED:-}" ]] && return 0
|
|
_CMUX_PROMPT_INSTALLED=1
|
|
|
|
local decl
|
|
decl="$(declare -p PROMPT_COMMAND 2>/dev/null || true)"
|
|
if [[ "$decl" == "declare -a"* ]]; then
|
|
local existing=0
|
|
local item
|
|
for item in "${PROMPT_COMMAND[@]}"; do
|
|
[[ "$item" == "_cmux_prompt_command" ]] && existing=1 && break
|
|
done
|
|
if (( existing == 0 )); then
|
|
PROMPT_COMMAND=("_cmux_prompt_command" "${PROMPT_COMMAND[@]}")
|
|
fi
|
|
else
|
|
case ";$PROMPT_COMMAND;" in
|
|
*";_cmux_prompt_command;"*) ;;
|
|
*)
|
|
if [[ -n "$PROMPT_COMMAND" ]]; then
|
|
PROMPT_COMMAND="_cmux_prompt_command;$PROMPT_COMMAND"
|
|
else
|
|
PROMPT_COMMAND="_cmux_prompt_command"
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
# Ensure Resources/bin is at the front of PATH. Shell init (.bashrc/.bash_profile)
|
|
# may prepend other dirs that push our wrapper behind the system claude binary.
|
|
_cmux_fix_path() {
|
|
if [[ -n "${GHOSTTY_BIN_DIR:-}" ]]; then
|
|
local bin_dir="${GHOSTTY_BIN_DIR%/MacOS}"
|
|
bin_dir="${bin_dir}/Resources/bin"
|
|
if [[ -d "$bin_dir" ]]; then
|
|
local new_path=":${PATH}:"
|
|
new_path="${new_path//:${bin_dir}:/:}"
|
|
new_path="${new_path#:}"
|
|
new_path="${new_path%:}"
|
|
PATH="${bin_dir}:${new_path}"
|
|
fi
|
|
fi
|
|
}
|
|
_cmux_fix_path
|
|
unset -f _cmux_fix_path
|
|
|
|
_cmux_install_prompt_command
|