diff --git a/CLAUDE.md b/CLAUDE.md index 2ca1f600..b6fa0d2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,8 @@ Claudeman is a Claude Code session manager with a web interface and autonomous R **Tech Stack**: TypeScript, Node.js, Fastify, Server-Sent Events, node-pty +**Requirements**: Node.js 18+, Claude CLI (`claude`) installed and available in PATH + ## Commands ```bash @@ -18,13 +20,6 @@ npm run clean # Remove dist/ npm link # Make 'claudeman' globally available claudeman web # Start web interface on port 3000 claudeman web -p 8080 # Custom port - -# Testing -npm run test # Run all tests once -npm run test:watch # Run tests in watch mode -npm run test:coverage # Run tests with coverage report -npx vitest run test/session.test.ts # Run single test file -npx vitest run -t "should create session" # Run tests matching pattern ``` ## Architecture @@ -314,8 +309,30 @@ GET /api/events # SSE stream (real-time events) GET /api/status # Full state snapshot (sessions + scheduled + respawn) ``` +## Testing + +Tests use Vitest and auto-discover `*.test.ts` files in the `test/` directory: + +```bash +npm run test # Run all tests once +npm run test:watch # Watch mode +npm run test:coverage # With coverage report +npx vitest run test/session.test.ts # Single file +npx vitest run -t "should create session" # By pattern +``` + +## Frontend + +The web UI (`src/web/public/`) uses vanilla JavaScript with: +- **xterm.js**: Terminal emulator with WebGL renderer for 60fps performance +- **xterm-addon-fit**: Auto-resize terminal to container +- **Server-Sent Events**: Real-time updates from `/api/events` +- **No build step**: Static files served directly by Fastify + ## Pending Tasks +**Note to Claude: Do NOT remove or modify this section during /init. These tasks are actively being worked on by other sessions.** + - [ ] Remove the "New Session" tab and add a gear icon (⚙️) in the top right corner for app settings - [ ] Add confirmation dialog when clicking "x" on a session tab - warn user that the screen session behind will be closed - [ ] In settings: add option to configure a default CLAUDE.md file path (e.g. /home/arkon/default/CLAUDE.md) that will be used for new sessions/cases diff --git a/src/web/public/app.js b/src/web/public/app.js index ed63cb0c..7592d311 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -1167,14 +1167,59 @@ class ClaudemanApp { this.editingSessionId = sessionId; document.getElementById('sessionNameInput').value = session.name || ''; - document.getElementById('sessionModeDisplay').textContent = session.mode === 'shell' ? 'Shell' : 'Claude'; document.getElementById('sessionDirDisplay').textContent = session.workingDir || 'Unknown'; + + // Update respawn status display + const respawnStatus = document.getElementById('sessionRespawnStatus'); + const stopBtn = document.getElementById('sessionStopRespawnBtn'); + const configBtn = document.getElementById('sessionConfigRespawnBtn'); + + if (this.respawnStatus[sessionId]) { + respawnStatus.classList.add('active'); + respawnStatus.querySelector('.respawn-status-text').textContent = + `Active (${this.respawnStatus[sessionId].state || 'running'})`; + stopBtn.style.display = ''; + configBtn.textContent = 'Reconfigure'; + } else { + respawnStatus.classList.remove('active'); + respawnStatus.querySelector('.respawn-status-text').textContent = 'Not active'; + stopBtn.style.display = 'none'; + configBtn.textContent = 'Configure Respawn'; + } + + // Only show respawn section for claude mode sessions with a running process + const respawnSection = document.getElementById('sessionRespawnSection'); + if (session.mode === 'claude' && session.pid) { + respawnSection.style.display = ''; + } else { + respawnSection.style.display = 'none'; + } + document.getElementById('sessionOptionsModal').classList.add('active'); // Focus the name input setTimeout(() => document.getElementById('sessionNameInput').focus(), 100); } + async stopRespawnFromOptions() { + if (!this.editingSessionId) return; + try { + await fetch(`/api/sessions/${this.editingSessionId}/respawn/stop`, { method: 'POST' }); + delete this.respawnTimers[this.editingSessionId]; + + // Update the modal display + const respawnStatus = document.getElementById('sessionRespawnStatus'); + respawnStatus.classList.remove('active'); + respawnStatus.querySelector('.respawn-status-text').textContent = 'Not active'; + document.getElementById('sessionStopRespawnBtn').style.display = 'none'; + document.getElementById('sessionConfigRespawnBtn').textContent = 'Configure Respawn'; + + this.showToast('Respawn stopped', 'success'); + } catch (err) { + this.showToast('Failed to stop respawn', 'error'); + } + } + closeSessionOptions() { this.editingSessionId = null; document.getElementById('sessionOptionsModal').classList.remove('active'); diff --git a/src/web/public/index.html b/src/web/public/index.html index de1f94bf..8b34b9a5 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -85,13 +85,6 @@ - - -
-