#!/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 session’s 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 ensure profile dir exists for session # dir 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 \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 \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 Ensure a Claude profile exists for claude-tmux dir Print the Claude profile dir for claude-tmux hook-ensure For tmux hooks (session-created / session-renamed) Env: CLAUDE_TEMPLATE_DIR Directory to copy into new profiles (optional) EOF ;; esac