diff --git a/CLAUDE.md b/CLAUDE.md
index d71e6dee..531588a8 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -381,17 +381,6 @@ npx agent-browser close
| POST | `/api/cases` | Create new case |
| GET | `/api/screens` | List screen sessions with stats |
-## CLI Commands (when using `claudeman` globally)
-
-```bash
-claudeman web [-p PORT] # Start web interface
-claudeman start [--dir PATH] # Start Claude session
-claudeman list # List sessions
-claudeman task add "PROMPT" # Add task to queue
-claudeman ralph start [--min-hours N] # Start autonomous loop
-claudeman status # Overall status
-```
-
## Keyboard Shortcuts
| Shortcut | Action |
@@ -405,12 +394,6 @@ claudeman status # Overall status
| `Ctrl+?` | Show keyboard shortcuts help |
| `Escape` | Close panels and modals |
-## UI Behaviors
-
-- **Auto-focus**: When only one session exists and none is active, it auto-selects
-- **Scroll preservation**: Expanding/collapsing Ralph panel preserves terminal scroll position
-- **Consistent icons**: Ralph and Monitor panels use same detach/attach icons (⧉/⊞)
-
## State Files
| File | Purpose |
@@ -421,6 +404,36 @@ claudeman status # Overall status
Cases created in `~/claudeman-cases/` by default.
+## Screen Session Manager (CLI Tool)
+
+Interactive bash tool for managing claudeman screen sessions directly from the terminal.
+
+```bash
+./scripts/screen-manager.sh # Interactive mode
+./scripts/screen-manager.sh list # List all sessions
+./scripts/screen-manager.sh attach 1 # Attach to session #1
+./scripts/screen-manager.sh kill 2,3 # Kill sessions 2 and 3
+./scripts/screen-manager.sh kill-all # Kill all sessions
+./scripts/screen-manager.sh info 1 # Show session #1 details
+```
+
+**Interactive Controls:**
+
+| Key | Action |
+|-----|--------|
+| `↑`/`↓` or `j`/`k` | Navigate sessions |
+| `Enter` | Attach to selected session |
+| `d` | Delete selected session |
+| `D` | Delete ALL sessions |
+| `i` | Show session info |
+| `q`/`Esc` | Quit |
+
+**Features:**
+- Reads from `~/.claudeman/screens.json` (claudeman's authoritative source)
+- Shows session name, running time, alive/dead status, mode
+- Flicker-free navigation (only updates changed rows)
+- Requires `jq` and `screen` to be installed
+
## Documentation
Extended documentation is available in the `docs/` directory:
@@ -432,8 +445,6 @@ Extended documentation is available in the `docs/` directory:
### Quick Reference: Ralph Wiggum Loops
-Ralph Wiggum is an autonomous loop technique that lets Claude work iteratively until completion criteria are met.
-
**Core Pattern**: `PHRASE` - The completion signal that tells the loop to stop.
**Skill Commands**:
@@ -443,31 +454,6 @@ Ralph Wiggum is an autonomous loop technique that lets Claude work iteratively u
/ralph-loop:help # Show help and usage
```
-**Best Practices** (see full guide for details):
-1. **Always set `--max-iterations`** - Safety limit to prevent runaway costs
-2. **Define clear success criteria** - Tests pass, lint clean, specific outputs
-3. **Use test-driven verification** - Built-in feedback loop
-4. **Include escape hatches** - "If stuck after N iterations, document and stop"
-5. **Commit frequently** - Recovery points in git history
-
**Claudeman Implementation**: The `InnerLoopTracker` class (`src/inner-loop-tracker.ts`) detects Ralph patterns in Claude output and tracks loop state, todos, and completion phrases. It auto-enables when Ralph-related patterns are detected.
-**API**:
-- `GET /api/sessions/:id/inner-state` - Loop state and todos
-- `POST /api/sessions/:id/inner-config` - Configure tracker
-
-**SSE Events**:
-- `session:innerLoopUpdate` - Loop state changes
-- `session:innerTodoUpdate` - Todo list updates
-- `session:innerCompletionDetected` - Completion phrase detected
-
-### External References
-
-**Official Anthropic Documentation**:
-- [Claude Code Hooks](https://code.claude.com/docs/en/hooks)
-- [Claude Code Best Practices](https://www.anthropic.com/engineering/claude-code-best-practices)
-- [Ralph Wiggum Plugin](https://github.com/anthropics/claude-code/tree/main/plugins/ralph-wiggum)
-
-**Community Resources**:
-- [Awesome Claude - Ralph Wiggum](https://awesomeclaude.ai/ralph-wiggum)
-- [Claude Fast - Autonomous Loops](https://claudefa.st/blog/guide/mechanics/autonomous-agent-loops)
+See [`docs/ralph-wiggum-guide.md`](docs/ralph-wiggum-guide.md) for full documentation on best practices, prompt templates, and troubleshooting.
diff --git a/README.md b/README.md
index be96c65f..9eca0e9f 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ Claudeman transforms Claude Code into an autonomous development powerhouse. Spaw
- [Token Management](#token-management)
- [Web Interface](#web-interface)
- [CLI Commands](#cli-commands)
+- [Screen Manager TUI](#screen-manager-interactive-tui)
- [API Reference](#api-reference)
- [Long-Running Sessions](#long-running-sessions)
- [Troubleshooting](#troubleshooting)
@@ -44,6 +45,7 @@ Claudeman transforms Claude Code into an autonomous development powerhouse. Spaw
| **Real-time Monitoring** | Track tokens, costs, memory, background tasks |
| **Session Persistence** | Screen sessions survive server restarts |
| **Inner Loop Tracking** | Detect Ralph loops running inside Claude Code |
+| **Screen Manager TUI** | Interactive terminal tool for managing screen sessions |
---
@@ -423,6 +425,28 @@ claudeman status
claudeman reset --force
```
+### Screen Manager (Interactive TUI)
+
+```bash
+./scripts/screen-manager.sh # Interactive mode with arrow navigation
+./scripts/screen-manager.sh list # List all sessions
+./scripts/screen-manager.sh attach 1 # Attach to session #1
+./scripts/screen-manager.sh kill 2,3 # Kill sessions 2 and 3
+./scripts/screen-manager.sh kill 1-5 # Kill sessions 1 through 5
+./scripts/screen-manager.sh kill-all # Kill all sessions
+./scripts/screen-manager.sh info 1 # Show session #1 details
+```
+
+**Interactive Controls:**
+- `↑`/`↓` or `j`/`k` - Navigate sessions
+- `Enter` - Attach to selected session (Ctrl+A D to detach)
+- `d` - Delete selected session
+- `D` - Delete ALL sessions
+- `i` - Show session info
+- `q`/`Esc` - Quit
+
+Requires `jq` and `screen` packages.
+
---
## API Reference
@@ -540,6 +564,16 @@ curl -X POST localhost:3000/api/sessions/:id/auto-clear \
### Screen Issues
+Use the interactive screen manager for easy session management:
+
+```bash
+./scripts/screen-manager.sh # Interactive TUI
+./scripts/screen-manager.sh list # List all sessions
+./scripts/screen-manager.sh kill-all # Kill all sessions
+```
+
+Or use raw commands:
+
```bash
screen -ls | grep claudeman # List screens
screen -X -S claudeman- quit # Kill specific
diff --git a/scripts/screen-manager.sh b/scripts/screen-manager.sh
new file mode 100755
index 00000000..abc67bbd
--- /dev/null
+++ b/scripts/screen-manager.sh
@@ -0,0 +1,494 @@
+#!/bin/bash
+#
+# Claudeman Screen Session Manager
+# Interactive tool with arrow key navigation
+# Reads from ~/.claudeman/screens.json
+#
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+MAGENTA='\033[0;35m'
+BOLD='\033[1m'
+DIM='\033[2m'
+NC='\033[0m'
+REVERSE='\033[7m'
+
+# Use the same path as claudeman (src/screen-manager.ts)
+SCREENS_FILE="${HOME}/.claudeman/screens.json"
+
+# Cached data
+CACHED_JSON=""
+CACHED_COUNT=0
+LAST_REFRESH=0
+REFRESH_INTERVAL=5 # Refresh data every 5 seconds
+
+# Cleanup on exit
+cleanup() {
+ tput cnorm 2>/dev/null
+ stty echo 2>/dev/null
+ echo ""
+}
+trap cleanup EXIT
+
+# Check dependencies
+check_dependencies() {
+ if ! command -v jq &> /dev/null; then
+ echo -e "${RED}Error: 'jq' is required. Install with: sudo apt install jq${NC}"
+ exit 1
+ fi
+ if ! command -v screen &> /dev/null; then
+ echo -e "${RED}Error: 'screen' is required. Install with: sudo apt install screen${NC}"
+ exit 1
+ fi
+}
+
+# Refresh cached data if needed
+refresh_cache() {
+ local now=$(date +%s)
+ if [[ $((now - LAST_REFRESH)) -ge $REFRESH_INTERVAL ]] || [[ -z "$CACHED_JSON" ]]; then
+ if [[ -f "$SCREENS_FILE" ]]; then
+ CACHED_JSON=$(cat "$SCREENS_FILE")
+ else
+ CACHED_JSON="[]"
+ fi
+ CACHED_COUNT=$(echo "$CACHED_JSON" | jq 'length')
+ LAST_REFRESH=$now
+ fi
+}
+
+# Force refresh
+force_refresh() {
+ LAST_REFRESH=0
+ refresh_cache
+}
+
+get_session_field() {
+ local index=$1 field=$2
+ echo "$CACHED_JSON" | jq -r ".[$index].$field // \"unknown\""
+}
+
+# Format duration
+format_duration() {
+ local s=$1 d=$((s/86400)) h=$(((s%86400)/3600)) m=$(((s%3600)/60))
+ if [[ $d -gt 0 ]]; then printf "%dd %dh" $d $h
+ elif [[ $h -gt 0 ]]; then printf "%dh %dm" $h $m
+ elif [[ $m -gt 0 ]]; then printf "%dm" $m
+ else printf "%ds" $s; fi
+}
+
+# Check if screen alive (cached per draw cycle)
+declare -A ALIVE_CACHE
+check_alive() {
+ local screen_name=$1
+ if [[ -z "${ALIVE_CACHE[$screen_name]+x}" ]]; then
+ if screen -ls 2>/dev/null | grep -q "$screen_name"; then
+ ALIVE_CACHE[$screen_name]=1
+ else
+ ALIVE_CACHE[$screen_name]=0
+ fi
+ fi
+ [[ "${ALIVE_CACHE[$screen_name]}" -eq 1 ]]
+}
+
+clear_alive_cache() {
+ ALIVE_CACHE=()
+}
+
+# Kill session by index
+kill_session() {
+ local idx=$1
+ local screen_name=$(get_session_field $idx "screenName")
+ local pid=$(get_session_field $idx "pid")
+
+ pkill -TERM -P $pid 2>/dev/null
+ kill -TERM -$pid 2>/dev/null
+ screen -S "$screen_name" -X quit 2>/dev/null
+ kill -KILL $pid 2>/dev/null
+
+ # Remove from JSON
+ CACHED_JSON=$(echo "$CACHED_JSON" | jq "del(.[$idx])")
+ echo "$CACHED_JSON" > "$SCREENS_FILE"
+ CACHED_COUNT=$(echo "$CACHED_JSON" | jq 'length')
+ clear_alive_cache
+}
+
+# Draw header (only once)
+draw_header() {
+ echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${BOLD}${CYAN}║ Claudeman Screen Session Manager ║${NC}"
+ echo -e "${BOLD}${CYAN}║ ${DIM}Press q or Esc to exit${CYAN} ║${NC}"
+ echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
+ echo ""
+}
+
+# Draw a single session row (no_newline=1 for partial updates)
+draw_row() {
+ local i=$1
+ local selected=$2
+ local no_newline=${3:-0}
+ local now=$(date +%s)
+
+ local name=$(echo "$CACHED_JSON" | jq -r ".[$i].name // \"unnamed\"")
+ local screen_name=$(echo "$CACHED_JSON" | jq -r ".[$i].screenName")
+ local created_at=$(echo "$CACHED_JSON" | jq -r ".[$i].createdAt")
+ local mode=$(echo "$CACHED_JSON" | jq -r ".[$i].mode")
+
+ local created_s=$((created_at / 1000))
+ local elapsed=$((now - created_s))
+ local running=$(format_duration $elapsed)
+
+ local status status_color
+ if check_alive "$screen_name"; then
+ status="● alive"
+ status_color="${GREEN}"
+ else
+ status="○ dead"
+ status_color="${RED}"
+ fi
+
+ [[ ${#name} -gt 20 ]] && name="${name:0:17}..."
+
+ local line_end=$'\n'
+ [[ $no_newline -eq 1 ]] && line_end=""
+
+ if [[ $i -eq $selected ]]; then
+ printf " ${REVERSE}${BOLD}▶ %-20s %-14s ${status_color}%-12s${NC}${REVERSE} %-8s ${NC}%s" \
+ "$name" "$running" "$status" "$mode" "$line_end"
+ else
+ printf " %-20s %-14s ${status_color}%-12s${NC} %-8s%s" \
+ "$name" "$running" "$status" "$mode" "$line_end"
+ fi
+}
+
+# Draw footer
+draw_footer() {
+ echo ""
+ echo -e " ${DIM}──────────────────────────────────────────────────────────────${NC}"
+ echo -e " ${GREEN}↑/↓${NC} Navigate ${GREEN}Enter${NC} Attach ${YELLOW}d${NC} Delete ${RED}D${NC} Delete All ${BLUE}i${NC} Info ${DIM}q${NC} Quit"
+}
+
+# Full redraw
+full_redraw() {
+ local selected=$1
+ clear
+ draw_header
+
+ if [[ $CACHED_COUNT -eq 0 ]]; then
+ echo -e " ${YELLOW}No sessions found${NC}"
+ echo -e " ${DIM}(checking: $SCREENS_FILE)${NC}"
+ echo ""
+ echo -e " ${DIM}Press q to quit${NC}"
+ return
+ fi
+
+ printf " ${BOLD}%-22s %-14s %-12s %-8s${NC}\n" "NAME" "RUNNING" "STATUS" "MODE"
+ echo -e " ${DIM}──────────────────────────────────────────────────────────────${NC}"
+
+ for ((i=0; i/dev/null || echo "unknown")
+
+ clear
+ echo -e "${BOLD}${CYAN}Session Details${NC}"
+ echo -e "${DIM}────────────────────────────────────────${NC}"
+ echo -e " ${BOLD}Name:${NC} $name"
+ echo -e " ${BOLD}Session ID:${NC} $session_id"
+ echo -e " ${BOLD}Screen:${NC} $screen_name"
+ echo -e " ${BOLD}PID:${NC} $pid"
+ echo -e " ${BOLD}Mode:${NC} $mode"
+ echo -e " ${BOLD}Directory:${NC} $working_dir"
+ echo -e " ${BOLD}Started:${NC} $started"
+ echo -e " ${BOLD}Running:${NC} $(format_duration $elapsed)"
+
+ if check_alive "$screen_name"; then
+ echo -e " ${BOLD}Status:${NC} ${GREEN}● alive${NC}"
+ local mem=$(ps -o rss= -p $pid 2>/dev/null | tr -d ' ')
+ [[ -n "$mem" ]] && echo -e " ${BOLD}Memory:${NC} $(echo "scale=1; $mem/1024" | bc) MB"
+ else
+ echo -e " ${BOLD}Status:${NC} ${RED}○ dead${NC}"
+ fi
+
+ echo ""
+ echo -e "${DIM}Press any key to continue...${NC}"
+ read -rsn1
+}
+
+# Confirm dialog
+confirm() {
+ local msg=$1
+ tput sc # Save cursor
+ tput cup $((CACHED_COUNT + 12)) 0
+ echo -en " ${YELLOW}$msg (y/n):${NC} "
+ read -rsn1 answer
+ tput rc # Restore cursor
+ [[ "$answer" == "y" || "$answer" == "Y" ]]
+}
+
+# Read single keypress (handles arrow keys)
+read_key() {
+ local key
+ IFS= read -rsn1 key
+
+ if [[ "$key" == $'\x1b' ]]; then
+ read -rsn2 -t 0.1 key
+ case "$key" in
+ '[A') echo "UP" ;;
+ '[B') echo "DOWN" ;;
+ *) echo "ESC" ;;
+ esac
+ elif [[ "$key" == "" ]]; then
+ echo "ENTER"
+ else
+ echo "$key"
+ fi
+}
+
+# Main interactive loop
+interactive_menu() {
+ local selected=0
+ local need_full_redraw=1
+
+ tput civis # Hide cursor
+ force_refresh
+ clear_alive_cache
+ full_redraw $selected
+ need_full_redraw=0
+
+ while true; do
+ # Adjust selection if out of bounds
+ [[ $selected -ge $CACHED_COUNT ]] && selected=$((CACHED_COUNT > 0 ? CACHED_COUNT - 1 : 0))
+ [[ $selected -lt 0 ]] && selected=0
+
+ # Redraw if needed
+ if [[ $need_full_redraw -eq 1 ]]; then
+ clear_alive_cache
+ full_redraw $selected
+ need_full_redraw=0
+ fi
+
+ local key=$(read_key)
+ local old_selected=$selected
+
+ case "$key" in
+ UP|k)
+ if [[ $selected -gt 0 ]]; then
+ ((selected--))
+ [[ $CACHED_COUNT -gt 0 ]] && update_selection $old_selected $selected
+ fi
+ ;;
+ DOWN|j)
+ if [[ $selected -lt $((CACHED_COUNT - 1)) ]]; then
+ ((selected++))
+ [[ $CACHED_COUNT -gt 0 ]] && update_selection $old_selected $selected
+ fi
+ ;;
+ ENTER)
+ if [[ $CACHED_COUNT -gt 0 ]]; then
+ local screen_name=$(get_session_field $selected "screenName")
+ if check_alive "$screen_name"; then
+ tput cnorm
+ clear
+ echo -e "${CYAN}Attaching... (Ctrl+A D to detach)${NC}"
+ sleep 0.3
+ screen -r "$screen_name"
+ tput civis
+ need_full_redraw=1
+ force_refresh
+ else
+ tput cup $((CACHED_COUNT + 12)) 0
+ echo -e " ${RED}Session is dead${NC} "
+ sleep 1
+ need_full_redraw=1
+ fi
+ fi
+ ;;
+ d|x)
+ if [[ $CACHED_COUNT -gt 0 ]]; then
+ local name=$(get_session_field $selected "name")
+ if confirm "Kill '$name'?"; then
+ kill_session $selected
+ need_full_redraw=1
+ else
+ need_full_redraw=1
+ fi
+ fi
+ ;;
+ D|X)
+ if [[ $CACHED_COUNT -gt 0 ]]; then
+ if confirm "Kill ALL $CACHED_COUNT sessions?"; then
+ for ((i=CACHED_COUNT-1; i>=0; i--)); do
+ kill_session $i
+ done
+ fi
+ need_full_redraw=1
+ fi
+ ;;
+ i)
+ if [[ $CACHED_COUNT -gt 0 ]]; then
+ show_info $selected
+ need_full_redraw=1
+ fi
+ ;;
+ q|Q|ESC)
+ clear
+ echo -e "${DIM}Goodbye!${NC}"
+ break
+ ;;
+ esac
+ done
+
+ tput cnorm
+}
+
+# Quick list (non-interactive)
+quick_list() {
+ force_refresh
+ local now=$(date +%s)
+
+ if [[ $CACHED_COUNT -eq 0 ]]; then
+ echo "No claudeman sessions found."
+ exit 0
+ fi
+
+ echo "Claudeman Sessions ($SCREENS_FILE):"
+ echo ""
+ printf "%-4s %-22s %-14s %-10s %-8s\n" "#" "NAME" "RUNNING" "STATUS" "MODE"
+ echo "─────────────────────────────────────────────────────────────────"
+
+ for ((i=0; i"; exit 1; }
+ force_refresh
+ local screen_name=$(get_session_field $(($2-1)) "screenName")
+ check_alive "$screen_name" && screen -r "$screen_name" || echo "Session dead or not found"
+ ;;
+ kill)
+ [[ -z "${2:-}" ]] && { echo "Usage: $0 kill "; exit 1; }
+ force_refresh
+ if [[ "$2" =~ ^[0-9]+-[0-9]+$ ]]; then
+ local start="${2%-*}" end="${2#*-}"
+ for ((i=end-1; i>=start-1; i--)); do kill_session $i 2>/dev/null; done
+ else
+ IFS=',' read -ra nums <<< "$2"
+ for num in $(echo "${nums[*]}" | tr ' ' '\n' | sort -rn); do
+ kill_session $((num-1)) 2>/dev/null
+ done
+ fi
+ echo "Done"
+ ;;
+ kill-all)
+ force_refresh
+ for ((i=CACHED_COUNT-1; i>=0; i--)); do kill_session $i; done
+ echo "All sessions killed"
+ ;;
+ info)
+ [[ -z "${2:-}" ]] && { echo "Usage: $0 info "; exit 1; }
+ force_refresh
+ show_info $(($2-1))
+ ;;
+ help|--help|-h) usage ;;
+ *) echo "Unknown: $1"; usage; exit 1 ;;
+ esac
+}
+
+main "$@"