Files
absolute-dotfiles/.local/bin/claude-tmux

174 lines
5.1 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# tmux-aware Claude profile helper
# Works in bash/zsh. No external deps except optional fzf and tmux.
set -euo pipefail
# Optional: set a directory to copy as a template when new profiles are created.
# Default: if ~/.claude exists it will be copied; otherwise create an empty dir.
CLAUDE_TEMPLATE_DIR="${CLAUDE_TEMPLATE_DIR:-}"
bin_exists() { command -v "$1" >/dev/null 2>&1; }
tmux_session_name() {
if [[ -n "${TMUX:-}" ]] && bin_exists tmux; then
tmux display-message -p '#S'
else
printf ''
fi
}
sanitize_session() {
# lowercase, spaces->-, keep alnum . _ : -
printf '%s' "$1" \
| tr '[:upper:] ' '[:lower:]-' \
| tr -cd '[:alnum:]._:-'
}
profile_dir_for_session() {
local s="$1"
case "$s" in
""|"default"|"personal"|"main") printf '%s\n' "$HOME/.claude" ;;
*) printf '%s\n' "$HOME/.claude-$(sanitize_session "$s")" ;;
esac
}
ensure_profile_dir() {
local dir="$1"
if [[ -d "$dir" ]]; then
return 0
fi
mkdir -p "$dir"
# Copy from template if provided, else from ~/.claude if present
local from="${CLAUDE_TEMPLATE_DIR:-}"
if [[ -z "$from" && -d "$HOME/.claude" ]]; then
from="$HOME/.claude"
fi
if [[ -n "$from" && -d "$from" ]]; then
# Copy only if empty
if [[ -z "$(ls -A "$dir")" ]]; then
cp -a "$from"/. "$dir"/
fi
fi
}
run_claude_with_profile() {
local dir="$1"; shift || true
if ! bin_exists claude; then
printf 'claude-tmux: "claude" binary not found in PATH\n' >&2
exit 127
fi
ensure_profile_dir "$dir"
CLAUDE_CONFIG_DIR="$dir" command claude "$@"
}
fzf_pick_session() {
local list new_label sel
if ! bin_exists tmux; then
printf 'claude-tmux: tmux not installed\n' >&2
return 1
fi
new_label="[new session]"
# Gather sessions, newest first
list="$(tmux list-sessions -F '#{session_last_attached} #{session_name}' 2>/dev/null \
| sort -rn | awk '{ $1=""; sub(/^ /,""); print }')"
if bin_exists fzf; then
sel="$(printf '%s\n%s\n' "$new_label" "$list" | fzf --prompt='tmux session > ' --height=40% --reverse)"
else
printf '%s\n%s\n' "$new_label" "$list"
printf 'Pick a session (type exact name or "%s"): ' "$new_label" >&2
read -r sel
fi
printf '%s' "$sel"
}
create_and_launch_in_session() {
local name="$1" dir cmd
dir="$(profile_dir_for_session "$name")"
ensure_profile_dir "$dir"
# Start a session running Claude with the sessions profile
cmd="CLAUDE_CONFIG_DIR='${dir}' claude"
if tmux has-session -t "$name" 2>/dev/null; then
# Session exists: send keys and attach
tmux send-keys -t "$name" "$cmd" C-m
exec tmux attach -t "$name"
else
exec tmux new-session -s "$name" "$cmd"
fi
}
attach_and_launch_in_existing_session() {
local name="$1" dir cmd
dir="$(profile_dir_for_session "$name")"
ensure_profile_dir "$dir"
cmd="CLAUDE_CONFIG_DIR='${dir}' claude"
tmux send-keys -t "$name" "$cmd" C-m
exec tmux attach -t "$name"
}
# Subcommands:
# ensure <session> ensure profile dir exists for session
# dir <session> print profile dir for session
# run [args...] run claude for current tmux session (inside tmux)
# pick fzf picker to choose or create a session and launch claude
# hook-ensure #{session_name} for tmux hooks
case "${1:-}" in
ensure)
shift; sess="${1:-}"
if [[ -z "$sess" ]]; then printf 'usage: claude-tmux ensure <session>\n' >&2; exit 2; fi
ensure_profile_dir "$(profile_dir_for_session "$sess")"
;;
dir)
shift; sess="${1:-}"
if [[ -z "$sess" ]]; then printf 'usage: claude-tmux dir <session>\n' >&2; exit 2; fi
profile_dir_for_session "$sess"
;;
run)
shift
sess="$(tmux_session_name)"
if [[ -z "$sess" ]]; then
printf 'claude-tmux: not inside tmux. Use "claude-tmux pick" or run inside a session.\n' >&2
exit 2
fi
run_claude_with_profile "$(profile_dir_for_session "$sess")" "$@"
;;
pick)
shift
sel="$(fzf_pick_session)"
if [[ -z "$sel" ]]; then exit 130; fi
if [[ "$sel" == "[new session]" ]]; then
printf 'New tmux session name: ' >&2
read -r newname
if [[ -z "$newname" ]]; then printf 'Empty name. Aborting.\n' >&2; exit 2; fi
create_and_launch_in_session "$newname"
else
attach_and_launch_in_existing_session "$sel"
fi
;;
hook-ensure)
# For tmux hooks like session-created / session-renamed
shift
sess="${1:-}"
if [[ -n "$sess" ]]; then
ensure_profile_dir "$(profile_dir_for_session "$sess")"
fi
;;
*)
cat <<'EOF'
claude-tmux: tmux-aware Claude profile helper
Usage:
claude-tmux run Run Claude for the current tmux session
claude-tmux pick Pick or create a tmux session with fzf, launch Claude, attach
claude-tmux ensure <session> Ensure a Claude profile exists for <session>
claude-tmux dir <session> Print the Claude profile dir for <session>
claude-tmux hook-ensure <name> For tmux hooks (session-created / session-renamed)
Env:
CLAUDE_TEMPLATE_DIR Directory to copy into new profiles (optional)
EOF
;;
esac