Files
Codeman/src/web/public/index.html
T

400 lines
19 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Claude Code session manager with web interface">
<meta name="theme-color" content="#0a0a0a">
<title>Claudeman</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
</head>
<body>
<div class="app">
<!-- Compact Header with Session Tabs -->
<header class="header">
<div class="header-brand">
<span class="logo">Claudeman</span>
</div>
<!-- Session Tabs -->
<div class="session-tabs" id="sessionTabs">
</div>
<div class="header-right">
<div class="header-font-controls">
<button class="btn-icon-header btn-sm" onclick="app.decreaseFontSize()" title="Decrease font (Ctrl+-)">A-</button>
<span class="font-size-display" id="fontSizeDisplay">14</span>
<button class="btn-icon-header btn-sm" onclick="app.increaseFontSize()" title="Increase font (Ctrl++)">A+</button>
</div>
<div class="header-system-stats" id="headerSystemStats" title="System resource usage">
<div class="stat-item">
<span class="stat-label">CPU</span>
<div class="stat-bar">
<div class="stat-bar-fill stat-bar-cpu" id="statCpuBar"></div>
</div>
<span class="stat-value" id="statCpu">--%</span>
</div>
<div class="stat-item">
<span class="stat-label">MEM</span>
<div class="stat-bar">
<div class="stat-bar-fill stat-bar-mem" id="statMemBar"></div>
</div>
<span class="stat-value" id="statMem">--</span>
</div>
</div>
<button class="btn-icon-header" onclick="app.openAppSettings()" title="App Settings">&#x2699;</button>
<div class="header-tokens" id="headerTokens" title="Total tokens across all sessions">0 tokens</div>
</div>
</header>
<!-- Timer Banner (shown when timed run is active) -->
<div class="timer-banner" id="timerBanner" style="display: none;">
<div class="timer-content">
<span class="timer-value" id="timerValue">00:00:00</span>
<div class="timer-progress"><div class="timer-progress-fill" id="timerProgress"></div></div>
<span class="timer-meta" id="timerMeta">0 tasks | $0.00</span>
</div>
<button class="btn-icon-only" onclick="app.stopCurrentRun()" title="Stop">&#x25A0;</button>
</div>
<!-- Respawn Banner -->
<div class="respawn-banner" id="respawnBanner" style="display: none;">
<span class="respawn-indicator">&#x21BB;</span>
<span class="respawn-state" id="respawnState">Watching</span>
<span class="respawn-cycles">Cycle <span id="respawnCycleCount">0</span></span>
<span class="respawn-timer" id="respawnTimer" style="display: none;"></span>
<span class="respawn-tokens" id="respawnTokens" style="display: none;"></span>
<span class="respawn-step" id="respawnStep"></span>
<button class="btn-icon-only" onclick="app.stopRespawn()" title="Stop Respawn">&#x25A0;</button>
</div>
<!-- Inner State Panel (Ralph Loop + Todo List detection) -->
<div class="inner-state-panel collapsed" id="innerStatePanel" style="display: none;">
<div class="inner-state-header" onclick="app.toggleInnerStatePanel()">
<span class="inner-state-toggle" id="innerStateToggle">&#x25B6;</span>
<span class="inner-state-title">Inner State</span>
</div>
<div class="inner-state-content" id="innerStateContent"></div>
</div>
<!-- Main Terminal Area -->
<main class="main">
<div class="terminal-container" id="terminalContainer"></div>
</main>
<!-- Bottom Toolbar -->
<footer class="toolbar">
<div class="toolbar-left">
<!-- Run Claude -->
<div class="toolbar-group">
<button class="btn-toolbar btn-claude" onclick="app.runClaude()" title="Run Claude (Ctrl+Enter)">
Run Claude
</button>
<div class="tab-count-group" title="Number of tabs to open (1-10)">
<button class="tab-count-btn" onclick="app.decrementTabCount()">−</button>
<input type="number" id="tabCount" class="tab-count-input" value="1" min="1" max="10" readonly>
<button class="tab-count-btn" onclick="app.incrementTabCount()">+</button>
</div>
<button class="btn-toolbar btn-shell" onclick="app.runShell()" title="Run Shell">
Run Shell
</button>
<div class="case-select-group">
<select id="quickStartCase" class="toolbar-select" title="Select case">
<option value="testcase">testcase</option>
</select>
<button class="btn-case-add" onclick="app.showCreateCaseModal()" title="Create new case">+</button>
</div>
</div>
<!-- Directory -->
<div class="toolbar-group">
<button class="btn-toolbar" onclick="app.toggleDirInput()" title="Set working directory">
<span id="dirDisplay">No directory</span>
</button>
<input type="text" id="dirInput" class="toolbar-input hidden" placeholder="Working directory..." onblur="app.hideDirInput()">
</div>
</div>
<div class="toolbar-right">
<!-- Buttons removed - use keyboard shortcuts instead -->
</div>
</footer>
<!-- Help Modal -->
<div class="modal" id="helpModal">
<div class="modal-backdrop" onclick="app.closeHelp()"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Keyboard Shortcuts</h3>
<button class="modal-close" onclick="app.closeHelp()">&times;</button>
</div>
<div class="modal-body">
<div class="shortcuts-grid">
<div><kbd>Ctrl</kbd>+<kbd>Enter</kbd></div><div>Run Claude</div>
<div><kbd>Ctrl</kbd>+<kbd>W</kbd></div><div>Close Session</div>
<div><kbd>Ctrl</kbd>+<kbd>Tab</kbd></div><div>Next Session</div>
<div><kbd>Ctrl</kbd>+<kbd>K</kbd></div><div>Kill All Sessions + Screens</div>
<div><kbd>Ctrl</kbd>+<kbd>L</kbd></div><div>Clear Terminal</div>
<div><kbd>Ctrl</kbd>+<kbd>+</kbd></div><div>Increase Font</div>
<div><kbd>Ctrl</kbd>+<kbd>-</kbd></div><div>Decrease Font</div>
<div><kbd>Ctrl</kbd>+<kbd>?</kbd></div><div>Show Help</div>
<div><kbd>Escape</kbd></div><div>Close Panels</div>
</div>
</div>
</div>
</div>
<!-- Monitor Panel (combined Process Monitor + Background Tasks) -->
<div class="monitor-panel" id="monitorPanel">
<div class="monitor-panel-header" id="monitorPanelHeader">
<span>Monitor</span>
<div class="monitor-panel-actions">
<button class="btn-toolbar btn-sm btn-danger" onclick="app.killAllSessions()" title="Kill all sessions and their screen processes">Kill All</button>
<button class="btn-icon-sm" onclick="app.reconcileScreens()" title="Refresh screens">&#x21BB;</button>
<button class="btn-icon-sm" onclick="app.toggleMonitorDetach()" title="Detach/Attach panel" id="monitorDetachBtn">&#x2197;</button>
<button class="btn-icon-sm" onclick="app.toggleMonitorPanel()" title="Toggle panel" id="monitorToggleBtn">&#x25BC;</button>
</div>
</div>
<div class="monitor-resize-handle" id="monitorResizeHandle"></div>
<div class="monitor-panel-body">
<div class="monitor-section">
<div class="monitor-section-header">
<span>Screen Sessions</span>
</div>
<div class="monitor-section-body" id="screenSessionsBody">
<div class="monitor-empty">No screen sessions</div>
</div>
</div>
<div class="monitor-section" id="backgroundTasksSection" style="display: none;">
<div class="monitor-section-header">
<span>Background Tasks</span>
<span class="task-stats" id="taskPanelStats">0 running</span>
</div>
<div class="monitor-section-body" id="backgroundTasksBody">
</div>
</div>
</div>
</div>
<!-- Session Options Modal (includes Respawn Settings) -->
<div class="modal" id="sessionOptionsModal">
<div class="modal-backdrop" onclick="app.closeSessionOptions()"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Session Options</h3>
<button class="modal-close" onclick="app.closeSessionOptions()">&times;</button>
</div>
<div class="modal-body">
<!-- Respawn Settings Section -->
<div class="session-respawn-section" id="sessionRespawnSection">
<div class="section-divider">
<span>Respawn Controller</span>
<div class="session-respawn-status" id="sessionRespawnStatus">
<span class="respawn-status-indicator"></span>
<span class="respawn-status-text">Not active</span>
</div>
</div>
<div class="form-row">
<label>Run Duration</label>
<div class="duration-presets">
<button type="button" class="duration-preset-btn" data-minutes="30" onclick="app.selectDurationPreset('30')">30m</button>
<button type="button" class="duration-preset-btn" data-minutes="60" onclick="app.selectDurationPreset('60')">1h</button>
<button type="button" class="duration-preset-btn" data-minutes="120" onclick="app.selectDurationPreset('120')">2h</button>
<button type="button" class="duration-preset-btn" data-minutes="240" onclick="app.selectDurationPreset('240')">4h</button>
<button type="button" class="duration-preset-btn" data-minutes="480" onclick="app.selectDurationPreset('480')">8h</button>
<button type="button" class="duration-preset-btn active" data-minutes="" onclick="app.selectDurationPreset('')">∞</button>
<div class="duration-custom">
<button type="button" class="duration-preset-btn" data-minutes="custom" onclick="app.selectDurationPreset('custom')">Custom</button>
<div class="input-suffix input-suffix-sm duration-custom-input">
<input type="number" id="modalRespawnDuration" value="" min="1" placeholder="min">
<span>min</span>
</div>
</div>
</div>
<span class="form-hint">How long to run the respawn loop</span>
</div>
<div class="form-row">
<label>Update Prompt</label>
<input type="text" id="modalRespawnPrompt" value="update all the docs and CLAUDE.md" placeholder="Prompt to send when idle">
</div>
<div class="form-row">
<label>After Update Prompt</label>
<div class="checkbox-group-inline">
<label class="checkbox-inline">
<input type="checkbox" id="modalRespawnSendClear" checked>
<span>Send /clear</span>
</label>
<label class="checkbox-inline">
<input type="checkbox" id="modalRespawnSendInit" checked>
<span>Send /init</span>
</label>
</div>
</div>
<div class="form-row">
<label>Kickstart Prompt</label>
<input type="text" id="modalRespawnKickstart" value="" placeholder="Optional: prompt if /init doesn't trigger work">
<span class="form-hint">Sent only when /init completes but Claude stays idle</span>
</div>
<div class="form-row">
<label>Auto-Compact Context</label>
<div class="auto-clear-row">
<label class="checkbox-inline">
<input type="checkbox" id="modalAutoCompactEnabled">
<span>Enable at</span>
</label>
<div class="input-suffix input-suffix-sm">
<input type="number" id="modalAutoCompactThreshold" value="110000" min="10000" max="500000" step="10000">
<span>tokens</span>
</div>
</div>
<input type="text" id="modalAutoCompactPrompt" value="" placeholder="Optional: focus prompt for compact" class="mt-sm">
<span class="form-hint">Summarizes context while preserving key information</span>
</div>
<div class="form-row">
<label>Auto-Clear Context</label>
<div class="auto-clear-row">
<label class="checkbox-inline">
<input type="checkbox" id="modalAutoClearEnabled">
<span>Enable at</span>
</label>
<div class="input-suffix input-suffix-sm">
<input type="number" id="modalAutoClearThreshold" value="140000" min="10000" max="500000" step="10000">
<span>tokens</span>
</div>
</div>
<span class="form-hint">Completely resets context (use higher threshold than compact)</span>
</div>
<div class="respawn-actions">
<button class="btn-toolbar btn-success" onclick="app.enableRespawnFromModal()" id="modalEnableRespawnBtn">Enable Respawn</button>
<button class="btn-toolbar btn-danger" onclick="app.stopRespawnFromModal()" id="modalStopRespawnBtn" style="display: none;">Stop Respawn</button>
</div>
</div>
</div>
</div>
</div>
<!-- Close Session Confirmation Modal -->
<div class="modal" id="closeConfirmModal">
<div class="modal-backdrop" onclick="app.cancelCloseSession()"></div>
<div class="modal-content modal-sm">
<div class="modal-header">
<h3>Close Session</h3>
<button class="modal-close" onclick="app.cancelCloseSession()">&times;</button>
</div>
<div class="modal-body">
<p class="modal-session-name" id="closeConfirmSessionName"></p>
</div>
<div class="close-options">
<button class="close-option" onclick="app.confirmCloseSession(false)">
<span class="close-option-title">Remove Tab</span>
<span class="close-option-desc">Screen session keeps running in background</span>
</button>
<button class="close-option close-option-danger" onclick="app.confirmCloseSession(true)">
<span class="close-option-title">Kill Claude</span>
<span class="close-option-desc">Terminate the session completely</span>
</button>
</div>
<div class="modal-footer-cancel">
<button class="btn-toolbar btn-sm" onclick="app.cancelCloseSession()">Cancel</button>
</div>
</div>
</div>
<!-- App Settings Modal -->
<div class="modal" id="appSettingsModal">
<div class="modal-backdrop" onclick="app.closeAppSettings()"></div>
<div class="modal-content">
<div class="modal-header">
<h3>App Settings</h3>
<button class="modal-close" onclick="app.closeAppSettings()">&times;</button>
</div>
<div class="modal-body">
<div class="form-row">
<label>Default CLAUDE.md Template Path</label>
<input type="text" id="appSettingsClaudeMdPath" placeholder="/path/to/CLAUDE.md template">
<span class="form-hint">Used when creating new cases. Leave empty for built-in template.</span>
</div>
<div class="form-row">
<label>Default Working Directory</label>
<input type="text" id="appSettingsDefaultDir" placeholder="~/projects">
<span class="form-hint">Default directory for new sessions.</span>
</div>
</div>
<div class="form-actions">
<button class="btn-toolbar" onclick="app.closeAppSettings()">Cancel</button>
<button class="btn-toolbar btn-primary" onclick="app.saveAppSettings()">Save</button>
</div>
</div>
</div>
<!-- Create Case Modal -->
<div class="modal" id="createCaseModal">
<div class="modal-backdrop" onclick="app.closeCreateCaseModal()"></div>
<div class="modal-content modal-sm">
<div class="modal-header">
<h3>Create New Case</h3>
<button class="modal-close" onclick="app.closeCreateCaseModal()">&times;</button>
</div>
<div class="modal-body">
<div class="form-row">
<label>Case Name</label>
<input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+">
<span class="form-hint">Letters, numbers, hyphens, underscores only</span>
</div>
<div class="form-row">
<label>Description (optional)</label>
<input type="text" id="newCaseDescription" placeholder="A brief description...">
</div>
</div>
<div class="form-actions">
<button class="btn-toolbar" onclick="app.closeCreateCaseModal()">Cancel</button>
<button class="btn-toolbar btn-primary" onclick="app.createCase()">Create</button>
</div>
</div>
</div>
<!-- Kill All Confirmation Modal -->
<div class="modal" id="killAllModal">
<div class="modal-backdrop" onclick="app.closeKillAllModal()"></div>
<div class="modal-content modal-sm">
<div class="modal-header">
<h3>Kill All Sessions</h3>
<button class="modal-close" onclick="app.closeKillAllModal()">&times;</button>
</div>
<div class="modal-body">
<p class="modal-warning-text">Choose how to close <span id="killAllCount">0</span> session(s):</p>
</div>
<div class="close-options">
<button class="close-option" onclick="app.confirmKillAll(false)">
<span class="close-option-title">Remove All Tabs</span>
<span class="close-option-desc">Screen sessions keep running in background</span>
</button>
<button class="close-option close-option-danger" onclick="app.confirmKillAll(true)">
<span class="close-option-title">Kill All Claude + Screens</span>
<span class="close-option-desc">Terminate everything completely</span>
</button>
</div>
<div class="modal-footer-cancel">
<button class="btn-toolbar btn-sm" onclick="app.closeKillAllModal()">Cancel</button>
</div>
</div>
</div>
<!-- Help Button -->
<button class="help-btn" onclick="app.showHelp()" title="Keyboard shortcuts (Ctrl+?)">?</button>
</div>
<script src="app.js"></script>
</body>
</html>