Files
config/bin/dt
T

117 lines
4.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# dt — gestionnaire de sessions claude-dans-dtach (1 session = 1 claude = 1 socket)
# Sockets dans ~/.dtach/. La creation se fait via l'alias `cc` (voir plus bas),
# pas ici : dt ne gere que listing / rattachement / kill.
#
# dt ls lister (nom, age, dossier, commande)
# dt at <nom> rattacher une session
# dt kill <nom> tuer une session
# dt --raw listing TSV (pour fzf)
# dt sock <nom> imprime le chemin du socket (utilitaire interne)
#
# Detacher une session attachee : Ctrl-\
#
# CREATION (a mettre dans ~/.bashrc) :
# dtach_claude() { dtach -c "$HOME/.dtach/${1:-claude-$(date +%H%M%S)}" -e '^\' claude; }
# alias cc='dtach_claude'
# # usage : cd ~/projets/seo && cc seo
set -uo pipefail
DTDIR="${DTACH_DIR:-$HOME/.dtach}"
mkdir -p "$DTDIR"
now=$(date +%s)
# PID(s) du process dtach pour ce socket, via la cmdline (ss ne liste pas les
# sockets dtach de maniere fiable selon la version). Matche -c/-n/-A/-N.
_pids_for_socket() {
local sock="$1"
pgrep -f -- "dtach -[cnAN] $sock" 2>/dev/null | sort -u
}
# Dossier courant du process : /proc sur Linux, lsof sur macOS (pas de /proc).
_cwd_of() {
if [ -d /proc ]; then
readlink -f "/proc/$1/cwd" 2>/dev/null
else
lsof -a -p "$1" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p'
fi
}
# Heure de demarrage REELLE du process (fiable, immuable), en epoch.
# date -d = GNU (Linux), date -j -f = BSD (macOS). Locale C : format lstart stable.
_starttime_of() {
local ls; ls=$(LC_ALL=C ps -o lstart= -p "$1" 2>/dev/null) || return
[ -n "$ls" ] || return
LC_ALL=C date -d "$ls" +%s 2>/dev/null \
|| LC_ALL=C date -j -f '%a %b %e %T %Y' "$ls" +%s 2>/dev/null
}
_age() {
local epoch="${1:-$now}"
# garde-fou : si epoch n'est pas numerique, age=0
[[ "$epoch" =~ ^[0-9]+$ ]] || epoch="$now"
local age="$(( now - epoch ))"
(( age < 0 )) && age=0
if (( age<60 )); then echo "${age}s"
elif (( age<3600 )); then echo "$(( age/60 ))m"
elif (( age<86400));then echo "$(( age/3600 ))h$(( (age%3600)/60 ))m"
else echo "$(( age/86400 ))j$(( (age%86400)/3600 ))h"; fi
}
# Une ligne TSV par session vivante. Nettoie les sockets morts au passage.
emit_raw() {
shopt -s nullglob
local sock name pids master cwd start
# Tilde via variable : "\~" en remplacement reste "\~" sous bash 3.2 (macOS).
local tilde='~'
for sock in "$DTDIR"/*; do
[ -S "$sock" ] || continue
name=$(basename "$sock")
pids=$(_pids_for_socket "$sock")
if [ -z "$pids" ]; then rm -f "$sock"; continue; fi # orphelin -> menage
master=$(echo "$pids" | head -1)
cwd=$(_cwd_of "$master"); cwd="${cwd:-?}"
start=$(_starttime_of "$master"); start="${start:-$now}"
# colonnes : NOM(cache pour fzf) TAB DOSSIER TAB AGE
printf '%s\t%s\t%s\n' "$name" "${cwd/#$HOME/$tilde}" "$(_age "$start")"
done
}
cmd_at() {
local name="${1:?nom requis}"; local sock="$DTDIR/$name"
[ -S "$sock" ] || { echo "Session '$name' introuvable."; return 1; }
exec dtach -a "$sock" -e '^\'
}
cmd_kill() {
local name="${1:?nom requis}"; local sock="$DTDIR/$name"
[ -S "$sock" ] || { echo "Session '$name' introuvable."; return 1; }
local master; master=$(_pids_for_socket "$sock" | head -1)
[ -n "$master" ] && kill "$master" 2>/dev/null
rm -f "$sock"
echo "Session '$name' tuee."
}
cmd_sock() { echo "$DTDIR/${1:?nom requis}"; }
cmd_ls() {
local rows; rows=$(emit_raw)
if [ -z "$rows" ]; then echo "Aucune session dtach."; return 0; fi
printf '\n \033[1;36mSessions claude\033[0m\n\n'
printf ' \033[2m%-40s %s\033[0m\n' DOSSIER AGE
printf '%s\n' "$rows" | while IFS=$'\t' read -r name cwd age; do
printf ' \033[34m%-40s\033[0m \033[33m%s\033[0m\n' "$cwd" "$age"
done
printf '\n'
}
case "${1:-}" in
ls) shift; cmd_ls "$@" ;;
at) shift; cmd_at "$@" ;;
kill) shift; cmd_kill "$@" ;;
sock) shift; cmd_sock "$@" ;;
--raw) emit_raw ;;
""|-h|--help) sed -n '2,18p' "$0" | sed -E 's/^# ?//' ;;
*) echo "Commande inconnue: $1"; sed -n '2,18p' "$0" | sed -E 's/^# ?//'; exit 1 ;;
esac