feat(claude-tmux): spawn detached tmux Claude Code session with Remote Control
New script that launches Claude Code (via claude-profile) inside a detached tmux session with --remote-control enabled, in a project's directory, named after the project (Title-cased, auto-incrementing #N on collision). Lets a session be started for later attach over VPN or control from the Claude app. Spec-first per repo conventions: specs/claude-tmux.spec.md, script, and tests/test-claude-tmux.sh (18 dryrun assertions, all passing). Claude-Session: https://claude.ai/code/session_014oa7T7z1h5n34vu8vX9zGT
This commit is contained in:
113
tests/test-claude-tmux.sh
Executable file
113
tests/test-claude-tmux.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CLAUDE_TMUX="$SCRIPT_DIR/scripts/claude-tmux"
|
||||
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; NC='\033[0m'
|
||||
pass=0; fail=0
|
||||
|
||||
assert_eq() {
|
||||
local desc="$1" expected="$2" actual="$3"
|
||||
if [[ "$expected" == "$actual" ]]; then
|
||||
echo -e "${GREEN}PASS${NC}: $desc"; ((pass++))
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: $desc"; echo " expected: $expected"; echo " actual: $actual"; ((fail++))
|
||||
fi
|
||||
}
|
||||
assert_contains() {
|
||||
local desc="$1" needle="$2" haystack="$3"
|
||||
if [[ "$haystack" == *"$needle"* ]]; then
|
||||
echo -e "${GREEN}PASS${NC}: $desc"; ((pass++))
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: $desc"; echo " needle: $needle"; echo " haystack: $haystack"; ((fail++))
|
||||
fi
|
||||
}
|
||||
assert_exit() {
|
||||
local desc="$1" expected="$2" actual="$3"
|
||||
if [[ "$expected" -eq "$actual" ]]; then
|
||||
echo -e "${GREEN}PASS${NC}: $desc"; ((pass++))
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: $desc"; echo " expected exit: $expected"; echo " actual exit: $actual"; ((fail++))
|
||||
fi
|
||||
}
|
||||
|
||||
# === Hermetic environment ===
|
||||
TEST_HOME="$(mktemp -d)"
|
||||
STUB_BIN="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$TEST_HOME" "$STUB_BIN"
|
||||
tmux kill-session -t "=Agent Runtimes" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Stub claude-profile on PATH so LAUNCH_CMD renders deterministically.
|
||||
cat > "$STUB_BIN/claude-profile" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$STUB_BIN/claude-profile"
|
||||
|
||||
export HOME="$TEST_HOME"
|
||||
export PATH="$STUB_BIN:$PATH"
|
||||
mkdir -p "$HOME/.claude-oreillyit-anthropic"
|
||||
echo "deep" > "$HOME/.claude-oreillyit-anthropic/last-mode"
|
||||
mkdir -p "$HOME/dev/claude/projects/agent-runtimes"
|
||||
mkdir -p "$HOME/dev/claude/octopus"
|
||||
|
||||
echo "=== claude-tmux tests ==="
|
||||
echo
|
||||
|
||||
# --- Basic dryrun: title-case name, mode from last-mode ---
|
||||
out=$("$CLAUDE_TMUX" agent-runtimes --dryrun 2>&1)
|
||||
assert_contains "dryrun shows title-cased session name" "Session name: Agent Runtimes" "$out"
|
||||
assert_contains "dryrun mode from last-mode (deep)" "Mode: deep" "$out"
|
||||
assert_contains "dryrun resolves projects/ folder" "Project: agent-runtimes (" "$out"
|
||||
assert_contains "dryrun launch cmd has remote-control name" '--remote-control Agent\ Runtimes' "$out"
|
||||
assert_contains "dryrun launch cmd passes --project basename" "--project agent-runtimes" "$out"
|
||||
assert_contains "dryrun attach hint quotes the name" 'tmux attach -t "Agent Runtimes"' "$out"
|
||||
|
||||
# --- Mode override ---
|
||||
out=$("$CLAUDE_TMUX" agent-runtimes --mode quick --dryrun 2>&1)
|
||||
assert_contains "dryrun honours --mode override" "Mode: quick" "$out"
|
||||
|
||||
# --- Name override ---
|
||||
out=$("$CLAUDE_TMUX" agent-runtimes --name "Custom Name" --dryrun 2>&1)
|
||||
assert_contains "dryrun honours --name override" "Session name: Custom Name" "$out"
|
||||
|
||||
# --- Top-level (non-projects) folder resolves ---
|
||||
out=$("$CLAUDE_TMUX" octopus --dryrun 2>&1)
|
||||
assert_contains "dryrun resolves top-level ~/dev/claude folder" "Session name: Octopus" "$out"
|
||||
|
||||
# --- Unknown project errors ---
|
||||
out=$("$CLAUDE_TMUX" no-such-project --dryrun 2>&1); rc=$?
|
||||
assert_exit "unknown project exits 1" 1 "$rc"
|
||||
assert_contains "unknown project error mentions search locations" "Project folder not found" "$out"
|
||||
|
||||
# --- No project, non-TTY errors ---
|
||||
out=$("$CLAUDE_TMUX" --dryrun </dev/null 2>&1); rc=$?
|
||||
assert_exit "no project (non-TTY) exits 1" 1 "$rc"
|
||||
assert_contains "no project error is helpful" "No project given" "$out"
|
||||
|
||||
# --- Missing profile errors ---
|
||||
out=$("$CLAUDE_TMUX" agent-runtimes --profile no-such-profile --dryrun 2>&1); rc=$?
|
||||
assert_exit "missing profile exits 1" 1 "$rc"
|
||||
assert_contains "missing profile error names dir" "Profile directory not found" "$out"
|
||||
|
||||
# --- Help ---
|
||||
out=$("$CLAUDE_TMUX" --help 2>&1); rc=$?
|
||||
assert_exit "--help exits 0" 0 "$rc"
|
||||
assert_contains "--help shows script name" "claude-tmux" "$out"
|
||||
|
||||
# --- Auto-increment against a live session ---
|
||||
if tmux new-session -d -s "Agent Runtimes" -c /tmp 2>/dev/null; then
|
||||
out=$("$CLAUDE_TMUX" agent-runtimes --dryrun 2>&1)
|
||||
assert_contains "auto-increments to #2 when base name taken" "Session name: Agent Runtimes #2" "$out"
|
||||
tmux kill-session -t "=Agent Runtimes" 2>/dev/null || true
|
||||
else
|
||||
echo -e "${GREEN}SKIP${NC}: auto-increment (tmux server unavailable)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== $pass passed, $fail failed ==="
|
||||
[[ "$fail" -eq 0 ]]
|
||||
Reference in New Issue
Block a user