diff --git a/src/tui/App.tsx b/src/tui/App.tsx index 6b3a5187..281a5088 100644 --- a/src/tui/App.tsx +++ b/src/tui/App.tsx @@ -74,6 +74,7 @@ export function App(): React.ReactElement { createCase, killSession, killAllSessions, + killAllScreensAndClaude, nextSession, prevSession, sendInput, @@ -339,6 +340,7 @@ export function App(): React.ReactElement { onSelectSession={handleSelectSession} onAttachSession={handleAttachSession} onDeleteSession={handleDeleteSession} + onDeleteAll={killAllScreensAndClaude} onCreateSession={handleCreateSession} onCreateCase={createCase} onRefresh={refreshSessions} diff --git a/src/tui/components/HelpOverlay.tsx b/src/tui/components/HelpOverlay.tsx index 61a46eed..e45c6d79 100644 --- a/src/tui/components/HelpOverlay.tsx +++ b/src/tui/components/HelpOverlay.tsx @@ -33,6 +33,7 @@ const SHORTCUT_GROUPS: ShortcutGroup[] = [ { key: 'Enter', description: 'View session in TUI' }, { key: 'a', description: 'Attach to screen (full terminal)' }, { key: 'd', description: 'Delete/kill selected session' }, + { key: 'D (Shift+d)', description: 'Delete ALL screens & Claude processes' }, { key: 'c', description: 'Switch to cases view' }, { key: 'n', description: 'Quick-start new session' }, { key: 'r', description: 'Refresh list' }, diff --git a/src/tui/components/StartScreen.tsx b/src/tui/components/StartScreen.tsx index 00d038cd..df922367 100644 --- a/src/tui/components/StartScreen.tsx +++ b/src/tui/components/StartScreen.tsx @@ -30,6 +30,7 @@ interface StartScreenProps { onSelectSession: (session: ScreenSession) => void; onAttachSession: (session: ScreenSession) => void; onDeleteSession: (session: ScreenSession) => void; + onDeleteAll: () => void; onCreateSession: (caseName?: string, count?: number, mode?: 'claude' | 'shell') => void; onCreateCase: (name: string) => Promise; onRefresh: () => void; @@ -80,6 +81,7 @@ export function StartScreen({ onSelectSession, onAttachSession, onDeleteSession, + onDeleteAll, onCreateSession, onCreateCase, onRefresh, @@ -168,6 +170,12 @@ export function StartScreen({ return; } + // 'D' (shift+d) to delete ALL screens and Claude processes + if (input === 'D') { + onDeleteAll(); + return; + } + // 'c' to switch to cases view if (input === 'c') { onRefreshCases(); @@ -513,6 +521,8 @@ export function StartScreen({ Attach [d] Delete + [D] + Delete ALL [q] Quit diff --git a/src/tui/hooks/useSessionManager.ts b/src/tui/hooks/useSessionManager.ts index 87997c26..79d85a08 100644 --- a/src/tui/hooks/useSessionManager.ts +++ b/src/tui/hooks/useSessionManager.ts @@ -156,6 +156,7 @@ interface SessionManagerState { createCase: (name: string) => Promise; killSession: (sessionId: string) => void; killAllSessions: () => void; + killAllScreensAndClaude: () => void; nextSession: () => void; prevSession: () => void; sendInput: (sessionId: string, input: string) => void; @@ -576,6 +577,44 @@ export function useSessionManager(): SessionManagerState { setTimeout(refreshSessions, 500); }, [sessions, refreshSessions]); + // Kill ALL claudeman screens and Claude processes (nuclear option) + const killAllScreensAndClaude = useCallback(() => { + // 1. Kill all tracked sessions first + for (const session of sessions) { + try { + execSync(`screen -S ${session.screenName} -X quit`, { + encoding: 'utf-8', + timeout: 5000, + }); + } catch { + // Ignore + } + } + + // 2. Kill any remaining claudeman screen sessions + try { + execSync('pkill -f "SCREEN.*claudeman"', { + encoding: 'utf-8', + timeout: 5000, + }); + } catch { + // May not find any, that's ok + } + + // 3. Kill all Claude CLI processes + try { + execSync('pkill -f "claude"', { + encoding: 'utf-8', + timeout: 5000, + }); + } catch { + // May not find any, that's ok + } + + setActiveSessionId(null); + setTimeout(refreshSessions, 500); + }, [sessions, refreshSessions]); + // Navigate to next session const nextSession = useCallback(() => { if (sessions.length === 0) return; @@ -684,6 +723,7 @@ export function useSessionManager(): SessionManagerState { createCase, killSession, killAllSessions, + killAllScreensAndClaude, nextSession, prevSession, sendInput,