#!/bin/sh # rclaude [dir] # # Durable Claude Code session, local or remote. Two layers of resilience: # # 1. tmux on survives terminal/transport drops (network, lid close, # ssh kill, terminal crash) — works even when is the local box # because the local terminal can also die independently. # 2. `claude --continue` resumes the per-directory session from disk after # anything kills the host itself (reboot, crash, OOM). # # Re-running with the same + always lands you back in the same # conversation: tmux reattaches if alive, claude --continue picks up from # ~/.claude/projects// otherwise. # # can be: # - any ssh-reachable target (Host alias, user@hostname, IP) # - "local", "localhost", or the local short/long hostname → no ssh, # just a local tmux session (still detachable with Ctrl-b d) # # Permission mode: --dangerously-skip-permissions is on by default — these # are sessions on hosts you own. Override with RCLAUDE_PERMS=default (or any # other --permission-mode value) if you want prompts back. # # Usage: # rclaude # local, current pwd (shorthand) # rclaude . # same # rclaude apricot # remote home dir on apricot # rclaude apricot ~/Code/@projects/foo # remote, specific dir # rclaude local . # local, current pwd (explicit) # rclaude local ~/Code/@projects/foo # local, specific dir # rclaude $(hostname) ~ # also local (hostname match) set -eu # Argument resolution: # `rclaude` → local, $PWD # `rclaude .` → local, $PWD # `rclaude ` → host, default dir (~ remote, $PWD local) # `rclaude ` → host, dir (with `.` resolving to $PWD) if [ $# -eq 0 ] || [ "${1:-}" = "." ]; then host=local dir=$PWD else host=$1 dir=${2:-} fi is_local() { case $1 in local|localhost|127.0.0.1|::1) return 0 ;; esac [ "$1" = "$(hostname)" ] && return 0 [ "$1" = "$(hostname -s 2>/dev/null)" ] && return 0 return 1 } # Defaults + `.` expansion now that we know whether we're local or remote. if is_local "$host"; then case ${dir:-.} in .|"") dir=$PWD ;; esac else if [ "$dir" = "." ]; then echo "error: '.' as dir requires a local target; pass an explicit remote path" >&2 exit 2 fi [ -z "$dir" ] && dir=\~ fi slug=$(printf %s "$dir" | sed -e 's|^[~/]*||' -e 's|[^A-Za-z0-9]|-|g') [ -z "$slug" ] && slug=home session="claude-$(whoami)-${slug}" perms=${RCLAUDE_PERMS:-bypass} case $perms in bypass) flag="--dangerously-skip-permissions" ;; *) flag="--permission-mode $perms" ;; esac if is_local "$host"; then # No ssh hop — local tmux. dir is already an absolute path here. cd "$dir" exec tmux new-session -A -s "$session" "exec claude --continue ${flag}" fi # Remote: tmux on the other side of an ssh -t. exec replaces the shell so # the tmux pane dies cleanly when claude exits. inner="cd ${dir} && exec claude --continue ${flag}" exec ssh -t "$host" "tmux new-session -A -s '${session}' \"${inner}\""