claudedo/shell/cc.sh
disqualifier 509d3ad3b3 v0.2.2: detached-session cleanup (shell ccclean + voice/CLI cleanup)
add a detached-only session cleanup in BOTH surfaces — the cc shell kit and the
claudedo daemon — so stale detached claude-* sessions can be cleared from either.

- cc.sh: ccclean kills DETACHED claude-* sessions only (tmux #{session_attached}==0),
  never attached; reports 'killed X, Y (2 detached); kept Z (attached)' or 'nothing to
  clean'. complements cckl (kill ALL incl attached), which stays the deliberate typed
  nuke. header updated; sources clean under bash + zsh.
- target.py: cleanup_detached() kills detached claude-* and returns (killed, kept)
  lists. it and list_sessions() now share ONE tmux query, _claude_sessions(), which
  returns (name, attached) pairs — single source for session enumeration.
- grammar: cleanup command (aliases detached/detach) routes to Action('system',
  'cleanup') — daemon-control, never injects. bare 'cleanup' and 'system cleanup' both
  accepted. 'clean'/'wipe' deliberately NOT used as aliases — they fuzzy-collide with
  erase's 'clear'/'wipe' (0.8 ratio); 'detached' is distinct. confirm command added for
  the opt-in confirm flow.
- daemon: system 'cleanup' -> _do_cleanup -> target.cleanup_detached, reports
  '[SYSTEM] cleanup: killed ...; kept ... (attached)'. behavior.cleanup_confirm
  (default false) announces and waits for a following 'confirm' before killing.
- CLI: 'claudedo cleanup' (self-contained tmux op, no running daemon needed).

safety model: detached-only means a misheard voice cleanup can NEVER kill the active
(attached) session. the only kill-attached path remains the shell cckl.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-27 20:01:17 -04:00

97 lines
2.8 KiB
Bash

# claudedo cc kit — claude-code-in-tmux session helpers.
# POSIX sh; sources cleanly under bash and zsh. side-effect-free on source
# (function definitions only — nothing runs at source time).
#
# every command REQUIRES an explicit project name. the session is always
# "claude-<name>", a stable speakable handle: "cc libs" -> claude-libs, which the
# voice daemon targets with "claudedo target libs" / "switch libs". the name->session
# mapping here MUST match target.py's session_name() in the daemon.
#
# cc <name> start or reattach to claude-<name>; writes ~/.claude-active
# ccr <name> reattach only (error if it doesn't exist); writes ~/.claude-active
# ccl list running claude- sessions
# cck <name> kill claude-<name>
# ccclean kill DETACHED claude- sessions only (never attached) — safe cleanup
# cckl kill ALL claude- sessions (including attached)
cc() {
if [ -z "$1" ]; then
echo "usage: cc <project-name>" >&2
return 1
fi
session="claude-$1"
echo "$session" > "$HOME/.claude-active"
if tmux has-session -t "$session" 2>/dev/null; then
tmux attach -t "$session"
else
tmux new-session -s "$session" "claude"
fi
}
ccr() {
if [ -z "$1" ]; then
echo "usage: ccr <project-name>" >&2
return 1
fi
session="claude-$1"
if tmux has-session -t "$session" 2>/dev/null; then
echo "$session" > "$HOME/.claude-active"
tmux attach -t "$session"
else
echo "no session '$session' — run 'cc $1' to start one" >&2
return 1
fi
}
ccl() {
tmux ls 2>/dev/null | grep '^claude-' || echo "no claude sessions running"
}
cck() {
if [ -z "$1" ]; then
echo "usage: cck <project-name>" >&2
return 1
fi
session="claude-$1"
if tmux kill-session -t "$session" 2>/dev/null; then
echo "killed $session"
else
echo "no session '$session'" >&2
return 1
fi
}
ccclean() {
killed=""
kept=""
while read -r name attached; do
case "$name" in
claude-*) ;;
*) continue ;;
esac
if [ "$attached" = "0" ]; then
if tmux kill-session -t "$name" 2>/dev/null; then
killed="${killed:+$killed, }$name"
fi
else
kept="${kept:+$kept, }$name"
fi
done <<EOF
$(tmux list-sessions -F '#{session_name} #{session_attached}' 2>/dev/null)
EOF
if [ -z "$killed" ]; then
echo "nothing to clean (no detached sessions)"
else
n=$(printf '%s' "$killed" | awk -F', ' '{print NF}')
msg="killed $killed ($n detached)"
[ -n "$kept" ] && msg="$msg; kept $kept (attached)"
echo "$msg"
fi
}
cckl() {
tmux ls 2>/dev/null | grep '^claude-' | cut -d: -f1 | while read -r s; do
tmux kill-session -t "$s" && echo "killed $s"
done
}