feat(tui): add terminal user interface with Ink

Add a full TUI for Claudeman using Ink (React for CLI):
- StartScreen: Shows existing sessions from screens.json
- TabBar: Session tabs with status indicators
- TerminalView: PTY output display with scrolling
- StatusBar: Session info and keyboard shortcuts
- HelpOverlay: Full keyboard shortcut reference

Features:
- Session discovery from ~/.claudeman/screens.json
- Tab navigation (Ctrl+Tab, Ctrl+1-9)
- Create new sessions (connects to web API)
- Kill sessions via screen commands
- Keyboard-driven navigation

Usage: claudeman tui

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-21 06:19:02 +01:00
co-authored by Claude Opus 4.5
parent b3dd0bcacb
commit 84dab25f38
15 changed files with 1906 additions and 2 deletions
+70
View File
@@ -0,0 +1,70 @@
/**
* @fileoverview TabBar component
*
* Horizontal tab bar showing all active sessions.
* Features:
* - Visual indication of active tab
* - Status indicator (idle/working)
* - Tab truncation for long names
*/
import React from 'react';
import { Box, Text } from 'ink';
import type { ScreenSession } from '../../types.js';
interface TabBarProps {
sessions: ScreenSession[];
activeSessionId: string | null;
onSelectSession: (sessionId: string) => void;
}
/**
* TabBar component for session navigation
*/
export function TabBar({
sessions,
activeSessionId,
}: TabBarProps): React.ReactElement {
if (sessions.length === 0) {
return (
<Box borderStyle="single" borderColor="gray" paddingX={1}>
<Text dimColor>No sessions</Text>
</Box>
);
}
return (
<Box borderStyle="single" borderColor="cyan" paddingX={1}>
{sessions.map((session, index) => {
const isActive = session.sessionId === activeSessionId;
const name = (session.name || 'unnamed').slice(0, 15);
// Status indicator: filled circle for alive, hollow for dead
const statusIcon = session.attached ? '\u25CF' : '\u25CB';
const statusColor = session.attached ? 'green' : 'red';
return (
<Box key={session.sessionId} marginRight={1}>
{isActive ? (
<Text backgroundColor="blue" color="white" bold>
{' '}
<Text color={statusColor}>{statusIcon}</Text> {name}{' '}
</Text>
) : (
<Text>
{' '}
<Text color={statusColor}>{statusIcon}</Text>{' '}
<Text dimColor>{name}</Text>{' '}
</Text>
)}
{index < sessions.length - 1 && <Text dimColor>|</Text>}
</Box>
);
})}
{/* Shortcut hint */}
<Box flexGrow={1} justifyContent="flex-end">
<Text dimColor>Ctrl+Tab: switch | Ctrl+W: close</Text>
</Box>
</Box>
);
}