rclaude: support local tmux mode (host=local|localhost|hostname)
This commit is contained in:
parent
ad7359b18e
commit
8a55bef58a
2 changed files with 44 additions and 23 deletions
12
README.md
12
README.md
|
|
@ -19,13 +19,15 @@ a detached tmux session on the remote so the work survives the SSH drop.
|
||||||
creates) a per-user tmux session on `<host>` named `claude-$(whoami)`.
|
creates) a per-user tmux session on `<host>` named `claude-$(whoami)`.
|
||||||
Detach with `Ctrl-b d`; transport drops don't kill the shell.
|
Detach with `Ctrl-b d`; transport drops don't kill the shell.
|
||||||
|
|
||||||
- **`bin/rclaude <host> [dir]`** — Remote, durable Claude Code session.
|
- **`bin/rclaude <host> [dir]`** — Durable Claude Code session, local or
|
||||||
Stacks two resilience layers: tmux survives transport drops, and
|
remote. Stacks two resilience layers: tmux survives terminal/transport
|
||||||
`claude --continue` resumes the per-directory session from
|
drops, and `claude --continue` resumes the per-directory session from
|
||||||
`~/.claude/projects/` after anything kills the host itself. Re-running
|
`~/.claude/projects/` after anything kills the host itself. Re-running
|
||||||
with the same `<host>` + `<dir>` always lands back in the same
|
with the same `<host>` + `<dir>` always lands back in the same
|
||||||
conversation. Defaults to `--dangerously-skip-permissions`; override with
|
conversation. `<host>` can be any ssh target, or `local`/`localhost`/the
|
||||||
`RCLAUDE_PERMS=default` (or any other `--permission-mode` value).
|
local hostname to skip ssh and use a local tmux session (still detachable
|
||||||
|
for terminal/network resilience). Defaults to
|
||||||
|
`--dangerously-skip-permissions`; override with `RCLAUDE_PERMS=default`.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
|
|
||||||
55
bin/rclaude
55
bin/rclaude
|
|
@ -1,38 +1,43 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# rclaude <host> [dir]
|
# rclaude <host> [dir]
|
||||||
#
|
#
|
||||||
# Remote, durable Claude Code session. Two layers of resilience stacked:
|
# Durable Claude Code session, local or remote. Two layers of resilience:
|
||||||
#
|
#
|
||||||
# 1. tmux on <host> survives transport drops (network, lid close, ssh kill)
|
# 1. tmux on <host> survives terminal/transport drops (network, lid close,
|
||||||
|
# ssh kill, terminal crash) — works even when <host> is the local box
|
||||||
|
# because the local terminal can also die independently.
|
||||||
# 2. `claude --continue` resumes the per-directory session from disk after
|
# 2. `claude --continue` resumes the per-directory session from disk after
|
||||||
# anything kills the host itself (reboot, crash, OOM)
|
# anything kills the host itself (reboot, crash, OOM).
|
||||||
#
|
#
|
||||||
# Re-running with the same <host> + <dir> always lands you back where you
|
# Re-running with the same <host> + <dir> always lands you back in the same
|
||||||
# were: tmux reattaches if alive, claude --continue picks up the conversation
|
# conversation: tmux reattaches if alive, claude --continue picks up from
|
||||||
# from ~/.claude/projects/<encoded-cwd>/ otherwise.
|
# ~/.claude/projects/<encoded-cwd>/ otherwise.
|
||||||
|
#
|
||||||
|
# <host> 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
|
# Permission mode: --dangerously-skip-permissions is on by default — these
|
||||||
# are remote sessions on hosts you own. Override with RCLAUDE_PERMS=default
|
# are sessions on hosts you own. Override with RCLAUDE_PERMS=default (or any
|
||||||
# (or any other --permission-mode value) if you want prompts back.
|
# other --permission-mode value) if you want prompts back.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# rclaude apricot # remote home dir
|
# rclaude apricot # remote home dir on apricot
|
||||||
# rclaude apricot ~/Code/@projects/foo # specific dir
|
# rclaude apricot ~/Code/@projects/foo # remote, specific dir
|
||||||
# rclaude apricot @proj/lilith # alias from project-paths.md
|
# rclaude local ~/Code/@projects/foo # local tmux-wrapped session
|
||||||
# # works if remote shell expands it
|
# rclaude $(hostname) ~ # same — detected as local
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
if [ $# -lt 1 ]; then
|
if [ $# -lt 1 ]; then
|
||||||
echo "usage: $0 <host> [dir] (dir defaults to remote \$HOME)" >&2
|
echo "usage: $0 <host> [dir] (dir defaults to remote/local \$HOME)" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
host=$1
|
host=$1
|
||||||
dir=${2:-\~}
|
dir=${2:-\~}
|
||||||
|
|
||||||
# tmux session name unique per (user, dir) so multiple Claude sessions on the
|
|
||||||
# same host don't collide. Slug is the path with non-alnum collapsed.
|
|
||||||
slug=$(printf %s "$dir" | sed -e 's|^[~/]*||' -e 's|[^A-Za-z0-9]|-|g')
|
slug=$(printf %s "$dir" | sed -e 's|^[~/]*||' -e 's|[^A-Za-z0-9]|-|g')
|
||||||
[ -z "$slug" ] && slug=home
|
[ -z "$slug" ] && slug=home
|
||||||
session="claude-$(whoami)-${slug}"
|
session="claude-$(whoami)-${slug}"
|
||||||
|
|
@ -43,8 +48,22 @@ case $perms in
|
||||||
*) flag="--permission-mode $perms" ;;
|
*) flag="--permission-mode $perms" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# cd then exec claude. exec replaces the shell so the tmux pane dies cleanly
|
is_local() {
|
||||||
# when claude exits (instead of leaving a stray shell behind).
|
case $1 in
|
||||||
inner="cd ${dir} && exec claude --continue ${flag}"
|
local|localhost|127.0.0.1|::1) return 0 ;;
|
||||||
|
esac
|
||||||
|
[ "$1" = "$(hostname)" ] && return 0
|
||||||
|
[ "$1" = "$(hostname -s 2>/dev/null)" ] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_local "$host"; then
|
||||||
|
# No ssh hop — just local tmux. eval expands ~ and env vars in dir.
|
||||||
|
eval "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}\""
|
exec ssh -t "$host" "tmux new-session -A -s '${session}' \"${inner}\""
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue