mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
feat: add interactive screen session manager CLI tool
- New scripts/screen-manager.sh for managing claudeman screen sessions - Interactive TUI with arrow key navigation (flicker-free) - Commands: list, attach, kill, kill-all, info - Reads from ~/.claudeman/screens.json (claudeman's authoritative source) - Shows session name, running time, alive/dead status, mode - Requires jq and screen packages - Updated CLAUDE.md and README.md with documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -381,17 +381,6 @@ npx agent-browser close
|
|||||||
| POST | `/api/cases` | Create new case |
|
| POST | `/api/cases` | Create new case |
|
||||||
| GET | `/api/screens` | List screen sessions with stats |
|
| 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
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
| Shortcut | Action |
|
| Shortcut | Action |
|
||||||
@@ -405,12 +394,6 @@ claudeman status # Overall status
|
|||||||
| `Ctrl+?` | Show keyboard shortcuts help |
|
| `Ctrl+?` | Show keyboard shortcuts help |
|
||||||
| `Escape` | Close panels and modals |
|
| `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
|
## State Files
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
@@ -421,6 +404,36 @@ claudeman status # Overall status
|
|||||||
|
|
||||||
Cases created in `~/claudeman-cases/` by default.
|
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
|
## Documentation
|
||||||
|
|
||||||
Extended documentation is available in the `docs/` directory:
|
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
|
### Quick Reference: Ralph Wiggum Loops
|
||||||
|
|
||||||
Ralph Wiggum is an autonomous loop technique that lets Claude work iteratively until completion criteria are met.
|
|
||||||
|
|
||||||
**Core Pattern**: `<promise>PHRASE</promise>` - The completion signal that tells the loop to stop.
|
**Core Pattern**: `<promise>PHRASE</promise>` - The completion signal that tells the loop to stop.
|
||||||
|
|
||||||
**Skill Commands**:
|
**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
|
/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.
|
**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**:
|
See [`docs/ralph-wiggum-guide.md`](docs/ralph-wiggum-guide.md) for full documentation on best practices, prompt templates, and troubleshooting.
|
||||||
- `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)
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ Claudeman transforms Claude Code into an autonomous development powerhouse. Spaw
|
|||||||
- [Token Management](#token-management)
|
- [Token Management](#token-management)
|
||||||
- [Web Interface](#web-interface)
|
- [Web Interface](#web-interface)
|
||||||
- [CLI Commands](#cli-commands)
|
- [CLI Commands](#cli-commands)
|
||||||
|
- [Screen Manager TUI](#screen-manager-interactive-tui)
|
||||||
- [API Reference](#api-reference)
|
- [API Reference](#api-reference)
|
||||||
- [Long-Running Sessions](#long-running-sessions)
|
- [Long-Running Sessions](#long-running-sessions)
|
||||||
- [Troubleshooting](#troubleshooting)
|
- [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 |
|
| **Real-time Monitoring** | Track tokens, costs, memory, background tasks |
|
||||||
| **Session Persistence** | Screen sessions survive server restarts |
|
| **Session Persistence** | Screen sessions survive server restarts |
|
||||||
| **Inner Loop Tracking** | Detect Ralph loops running inside Claude Code |
|
| **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
|
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
|
## API Reference
|
||||||
@@ -540,6 +564,16 @@ curl -X POST localhost:3000/api/sessions/:id/auto-clear \
|
|||||||
|
|
||||||
### Screen Issues
|
### 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
|
```bash
|
||||||
screen -ls | grep claudeman # List screens
|
screen -ls | grep claudeman # List screens
|
||||||
screen -X -S claudeman-<id> quit # Kill specific
|
screen -X -S claudeman-<id> quit # Kill specific
|
||||||
|
|||||||
Executable
+494
@@ -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<CACHED_COUNT; i++)); do
|
||||||
|
draw_row $i $selected
|
||||||
|
done
|
||||||
|
|
||||||
|
draw_footer
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update just the selection (fast, no flicker)
|
||||||
|
update_selection() {
|
||||||
|
local old_sel=$1
|
||||||
|
local new_sel=$2
|
||||||
|
local header_lines=7 # Lines before session list
|
||||||
|
|
||||||
|
# Move to old selection, clear line, redraw as unselected
|
||||||
|
tput cup $((header_lines + old_sel)) 0
|
||||||
|
tput el # Clear to end of line
|
||||||
|
draw_row $old_sel -1 1 # -1 means not selected, 1 means no newline
|
||||||
|
|
||||||
|
# Move to new selection, clear line, redraw as selected
|
||||||
|
tput cup $((header_lines + new_sel)) 0
|
||||||
|
tput el # Clear to end of line
|
||||||
|
draw_row $new_sel $new_sel 1 # selected, no newline
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show session info
|
||||||
|
show_info() {
|
||||||
|
local idx=$1
|
||||||
|
local now=$(date +%s)
|
||||||
|
|
||||||
|
local name=$(get_session_field $idx "name")
|
||||||
|
local session_id=$(get_session_field $idx "sessionId")
|
||||||
|
local screen_name=$(get_session_field $idx "screenName")
|
||||||
|
local pid=$(get_session_field $idx "pid")
|
||||||
|
local created_at=$(get_session_field $idx "createdAt")
|
||||||
|
local mode=$(get_session_field $idx "mode")
|
||||||
|
local working_dir=$(get_session_field $idx "workingDir")
|
||||||
|
|
||||||
|
local created_s=$((created_at / 1000))
|
||||||
|
local elapsed=$((now - created_s))
|
||||||
|
local started=$(date -d "@$created_s" '+%Y-%m-%d %H:%M:%S' 2>/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<CACHED_COUNT; i++)); do
|
||||||
|
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="alive"
|
||||||
|
check_alive "$screen_name" || status="dead"
|
||||||
|
|
||||||
|
[[ ${#name} -gt 20 ]] && name="${name:0:17}..."
|
||||||
|
|
||||||
|
printf "%-4s %-22s %-14s %-10s %-8s\n" "[$((i+1))]" "$name" "$running" "$status" "$mode"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
Claudeman Screen Session Manager
|
||||||
|
|
||||||
|
Reads from: $SCREENS_FILE
|
||||||
|
|
||||||
|
Usage: $0 [command]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
(none) Interactive mode with arrow key navigation
|
||||||
|
list, ls List all sessions
|
||||||
|
attach N Attach to session #N
|
||||||
|
kill N Kill session #N (or N,M or N-M)
|
||||||
|
kill-all Kill all sessions
|
||||||
|
info N Show session #N details
|
||||||
|
help Show this help
|
||||||
|
|
||||||
|
Interactive Controls:
|
||||||
|
↑/↓ or j/k Navigate
|
||||||
|
Enter Attach to selected session
|
||||||
|
d Delete selected session
|
||||||
|
D Delete ALL sessions
|
||||||
|
i Show session info
|
||||||
|
q/Esc Quit
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 # Interactive mode
|
||||||
|
$0 list # List sessions
|
||||||
|
$0 attach 1 # Attach to session 1
|
||||||
|
$0 kill 2,3 # Kill sessions 2 and 3
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
main() {
|
||||||
|
check_dependencies
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
"") interactive_menu ;;
|
||||||
|
list|ls) quick_list ;;
|
||||||
|
attach)
|
||||||
|
[[ -z "${2:-}" ]] && { echo "Usage: $0 attach <N>"; 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 <N|N,M|N-M>"; 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 <N>"; exit 1; }
|
||||||
|
force_refresh
|
||||||
|
show_info $(($2-1))
|
||||||
|
;;
|
||||||
|
help|--help|-h) usage ;;
|
||||||
|
*) echo "Unknown: $1"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user