Case Study: Building a WebSocket CLI Server
Project Background
This case study demonstrates how to use Superpowers to build a WebSocket-enabled CLI server. The server supports the brainstorming skill's visual interface, providing:
- HTTP serving: Visual interface delivery
- WebSocket communication: Real-time bidirectional communication
- File watching: Auto-reload interface
- Event logging: Persist user choices
Workflow Demonstration
Phase 1: Brainstorming
Trigger scenario: Need to build a real-time visualization tool for brainstorming
AI behavior:
- Asks questions to understand requirements: What features does the server need?
- Explores technical approaches: WebSocket vs HTTP long-polling?
- Presents design in sections: Protocol design → File structure → Interface definition
Output: docs/design.md design document
Phase 2: Writing Plans
AI behavior:
- Breaks design into 2-5 minute tasks
- Each task includes: file path, specific code, verification steps
Example task breakdown:
markdown
## Task 1: Create Basic HTTP Server
- File: `server.js`
- Steps:
1. Import http module
2. Create server listening on port
3. Serve HTML page
- Verify: curl localhost:3000 returns HTML
## Task 2: Add WebSocket Support
- File: `server.js`
- Steps:
1. Import ws module
2. Handle WebSocket upgrade requests
3. Implement message broadcast
- Verify: Use wscat to connect and send messagePhase 3: TDD Implementation
RED: Write Failing Test
javascript
// server.test.js
await test('serves waiting page when no screens exist', async () => {
const res = await fetch(`http://localhost:${TEST_PORT}/`);
assert.strictEqual(res.status, 200);
assert(res.body.includes('Waiting for Claude'));
});GREEN: Minimal Implementation
javascript
// server.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<html><body>Waiting for Claude</body></html>');
});
server.listen(PORT);REFACTOR: Optimize Code
Add template system, WebSocket support, file watching, etc.
Phase 4: Debugging
Problem: Broadcast fails after WebSocket client disconnects
Diagnosis flow:
Isolate the problem
bash# Start server node server.js # Connect and immediately disconnect wscat -c ws://localhost:3000 # Ctrl+C to disconnect # Observe error # Error: Cannot send to closed WebSocketRoot cause analysis
- Disconnected client not removed from connection list
- Broadcast attempts to send to closed connection
Fix
javascriptws.on('close', () => { const index = clients.indexOf(ws); if (index > -1) clients.splice(index, 1); });
Phase 5: Completion
Verification checklist:
- [ ] All tests pass
- [ ] No console errors
- [ ] Documentation updated
- [ ] Code reviewed
Output: Merge to main branch or create PR
Key Code
HTTP Serving
javascript
const http = require('http');
const fs = require('fs');
const path = require('path');
function serveHtml(dir, port) {
const server = http.createServer((req, res) => {
if (req.url !== '/') {
res.writeHead(404);
return res.end('Not found');
}
// Find newest HTML file
const files = fs.readdirSync(dir)
.filter(f => f.endsWith('.html'))
.map(f => ({
name: f,
mtime: fs.statSync(path.join(dir, f)).mtime
}))
.sort((a, b) => b.mtime - a.mtime);
if (files.length === 0) {
res.writeHead(200, { 'Content-Type': 'text/html' });
return res.end('<h1>Waiting for Claude...</h1>');
}
const content = fs.readFileSync(path.join(dir, files[0].name), 'utf-8');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content);
});
server.listen(port);
console.log(JSON.stringify({ type: 'server-started', port }));
}WebSocket Communication
javascript
const WebSocket = require('ws');
function setupWebSocket(server, dir) {
const wss = new WebSocket.Server({ server });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('message', (data) => {
const event = JSON.parse(data.toString());
// Log user choices to file
if (event.choice) {
fs.appendFileSync(
path.join(dir, '.events'),
JSON.stringify(event) + '\n'
);
}
// Output to stdout for Claude to read
console.log(JSON.stringify({ ...event, source: 'user-event' }));
});
ws.on('close', () => clients.delete(ws));
});
return clients;
}File Watching
javascript
const fs = require('fs');
function watchFiles(dir, clients) {
fs.watch(dir, (eventType, filename) => {
if (!filename?.endsWith('.html')) return;
// Clear previous event log
const eventsFile = path.join(dir, '.events');
if (fs.existsSync(eventsFile)) {
fs.unlinkSync(eventsFile);
}
// Notify all clients to reload
const message = JSON.stringify({ type: 'reload' });
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
console.log(JSON.stringify({ type: 'screen-updated', file: filename }));
});
}Key Learnings
1. Using the brainstorming Skill
- AI asks questions proactively instead of writing code directly
- Design document presented in sections for understanding
- Technical approaches compared and analyzed
2. Task Breakdown Techniques
- Each task takes 2-5 minutes
- Clear verification criteria
- Task dependencies clearly identified
3. TDD Practice
- Write tests first, then code
- RED-GREEN-REFACTOR cycle
- Tests serve as documentation
4. Systematic Debugging
- Isolate problem, minimal reproduction
- Root cause analysis, not surface fixes
- Verify edge cases after fix
Extension Exercises
- Add Authentication: Add token validation for WebSocket connections
- Support Multiple Rooms: Implement independent brainstorming sessions
- Persist History: Save user choices to a database
Related Skills
- brainstorming - Requirements exploration and design
- writing-plans - Task breakdown
- test-driven-development - TDD practice
- systematic-debugging - Systematic debugging