From e34f495368f96a500d671c2280102232b4957ff0 Mon Sep 17 00:00:00 2001 From: David Ibia Date: Sun, 9 Nov 2025 14:09:30 +0100 Subject: [PATCH] feat(bin): add new scripts for managing Claude profiles and sessions. --- .local/bin/brave-debug | 2 + .local/bin/cl | 15 ++++ .local/bin/claude-tmux | 173 +++++++++++++++++++++++++++++++++++++++++ .local/bin/cldir | 16 ++++ 4 files changed, 206 insertions(+) create mode 100755 .local/bin/brave-debug create mode 100755 .local/bin/cl create mode 100755 .local/bin/claude-tmux create mode 100755 .local/bin/cldir diff --git a/.local/bin/brave-debug b/.local/bin/brave-debug new file mode 100755 index 0000000..323a334 --- /dev/null +++ b/.local/bin/brave-debug @@ -0,0 +1,2 @@ +#!/bin/bash +/Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser --remote-debugging-port=9222 --user-data-dir=/tmp/brave-debug "$@" diff --git a/.local/bin/cl b/.local/bin/cl new file mode 100755 index 0000000..c85ce50 --- /dev/null +++ b/.local/bin/cl @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +function usecl() { + # Inside tmux: launch Claude with the session's profile + if [[ -n "${TMUX:-}" ]]; then + ~/.local/bin/claude-tmux run "$@" + else + # Not in tmux: pick or create a session, attach, and launch Claude there + ~/.local/bin/claude-tmux pick + fi +} + +usecl "$@" diff --git a/.local/bin/claude-tmux b/.local/bin/claude-tmux new file mode 100755 index 0000000..dba5523 --- /dev/null +++ b/.local/bin/claude-tmux @@ -0,0 +1,173 @@ +#!/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 diff --git a/.local/bin/cldir b/.local/bin/cldir new file mode 100755 index 0000000..916fda5 --- /dev/null +++ b/.local/bin/cldir @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail + +function usecldir() { + # Optional convenience: quickly print the profile dir for this session + if [[ -n "${TMUX:-}" ]]; then + local s + s="$(tmux display-message -p '#S')" + ~/.local/bin/claude-tmux dir "$s" + else + echo "$HOME/.claude" + fi +} + +usecldir "$@"