|
|
@@ -0,0 +1,571 @@
|
|
|
+# Visual Brainstorming Companion Implementation Plan
|
|
|
+
|
|
|
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
+
|
|
|
+**Goal:** Give Claude a browser-based visual companion for brainstorming sessions - show mockups, prototypes, and interactive choices alongside terminal conversation.
|
|
|
+
|
|
|
+**Architecture:** Claude writes HTML to a temp file. A local Node.js server watches that file and serves it with an auto-injected helper library. User interactions flow via WebSocket to server stdout, which Claude sees in background task output.
|
|
|
+
|
|
|
+**Tech Stack:** Node.js, Express, ws (WebSocket), chokidar (file watching)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 1: Create the Server Foundation
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `lib/brainstorm-server/index.js`
|
|
|
+- Create: `lib/brainstorm-server/package.json`
|
|
|
+
|
|
|
+**Step 1: Create package.json**
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "name": "brainstorm-server",
|
|
|
+ "version": "1.0.0",
|
|
|
+ "description": "Visual brainstorming companion server for Claude Code",
|
|
|
+ "main": "index.js",
|
|
|
+ "dependencies": {
|
|
|
+ "chokidar": "^3.5.3",
|
|
|
+ "express": "^4.18.2",
|
|
|
+ "ws": "^8.14.2"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**Step 2: Create minimal server that starts**
|
|
|
+
|
|
|
+```javascript
|
|
|
+const express = require('express');
|
|
|
+const http = require('http');
|
|
|
+const WebSocket = require('ws');
|
|
|
+const chokidar = require('chokidar');
|
|
|
+const fs = require('fs');
|
|
|
+const path = require('path');
|
|
|
+
|
|
|
+const PORT = process.env.BRAINSTORM_PORT || 3333;
|
|
|
+const SCREEN_FILE = process.env.BRAINSTORM_SCREEN || '/tmp/brainstorm/screen.html';
|
|
|
+const SCREEN_DIR = path.dirname(SCREEN_FILE);
|
|
|
+
|
|
|
+// Ensure screen directory exists
|
|
|
+if (!fs.existsSync(SCREEN_DIR)) {
|
|
|
+ fs.mkdirSync(SCREEN_DIR, { recursive: true });
|
|
|
+}
|
|
|
+
|
|
|
+// Create default screen if none exists
|
|
|
+if (!fs.existsSync(SCREEN_FILE)) {
|
|
|
+ fs.writeFileSync(SCREEN_FILE, `<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <title>Brainstorm Companion</title>
|
|
|
+ <style>
|
|
|
+ body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
|
|
+ h1 { color: #333; }
|
|
|
+ p { color: #666; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <h1>Brainstorm Companion</h1>
|
|
|
+ <p>Waiting for Claude to push a screen...</p>
|
|
|
+</body>
|
|
|
+</html>`);
|
|
|
+}
|
|
|
+
|
|
|
+const app = express();
|
|
|
+const server = http.createServer(app);
|
|
|
+const wss = new WebSocket.Server({ server });
|
|
|
+
|
|
|
+// Track connected browsers for reload notifications
|
|
|
+const clients = new Set();
|
|
|
+
|
|
|
+wss.on('connection', (ws) => {
|
|
|
+ clients.add(ws);
|
|
|
+ ws.on('close', () => clients.delete(ws));
|
|
|
+
|
|
|
+ ws.on('message', (data) => {
|
|
|
+ // User interaction event - write to stdout for Claude
|
|
|
+ const event = JSON.parse(data.toString());
|
|
|
+ console.log(JSON.stringify({ type: 'user-event', ...event }));
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+// Serve current screen with helper.js injected
|
|
|
+app.get('/', (req, res) => {
|
|
|
+ let html = fs.readFileSync(SCREEN_FILE, 'utf-8');
|
|
|
+
|
|
|
+ // Inject helper script before </body>
|
|
|
+ const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
|
|
|
+ const injection = `<script>\n${helperScript}\n</script>`;
|
|
|
+
|
|
|
+ if (html.includes('</body>')) {
|
|
|
+ html = html.replace('</body>', `${injection}\n</body>`);
|
|
|
+ } else {
|
|
|
+ html += injection;
|
|
|
+ }
|
|
|
+
|
|
|
+ res.type('html').send(html);
|
|
|
+});
|
|
|
+
|
|
|
+// Watch for screen file changes
|
|
|
+chokidar.watch(SCREEN_FILE).on('change', () => {
|
|
|
+ console.log(JSON.stringify({ type: 'screen-updated', file: SCREEN_FILE }));
|
|
|
+ // Notify all browsers to reload
|
|
|
+ clients.forEach(ws => {
|
|
|
+ if (ws.readyState === WebSocket.OPEN) {
|
|
|
+ ws.send(JSON.stringify({ type: 'reload' }));
|
|
|
+ }
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+server.listen(PORT, '127.0.0.1', () => {
|
|
|
+ console.log(JSON.stringify({ type: 'server-started', port: PORT, url: `http://localhost:${PORT}` }));
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**Step 3: Run npm install**
|
|
|
+
|
|
|
+Run: `cd lib/brainstorm-server && npm install`
|
|
|
+Expected: Dependencies installed
|
|
|
+
|
|
|
+**Step 4: Test server starts**
|
|
|
+
|
|
|
+Run: `cd lib/brainstorm-server && timeout 3 node index.js || true`
|
|
|
+Expected: See JSON with `server-started` and port info
|
|
|
+
|
|
|
+**Step 5: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add lib/brainstorm-server/
|
|
|
+git commit -m "feat: add brainstorm server foundation"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 2: Create the Helper Library
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `lib/brainstorm-server/helper.js`
|
|
|
+
|
|
|
+**Step 1: Create helper.js with event auto-capture**
|
|
|
+
|
|
|
+```javascript
|
|
|
+(function() {
|
|
|
+ const WS_URL = 'ws://' + window.location.host;
|
|
|
+ let ws = null;
|
|
|
+ let eventQueue = [];
|
|
|
+
|
|
|
+ function connect() {
|
|
|
+ ws = new WebSocket(WS_URL);
|
|
|
+
|
|
|
+ ws.onopen = () => {
|
|
|
+ // Send any queued events
|
|
|
+ eventQueue.forEach(e => ws.send(JSON.stringify(e)));
|
|
|
+ eventQueue = [];
|
|
|
+ };
|
|
|
+
|
|
|
+ ws.onmessage = (msg) => {
|
|
|
+ const data = JSON.parse(msg.data);
|
|
|
+ if (data.type === 'reload') {
|
|
|
+ window.location.reload();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ ws.onclose = () => {
|
|
|
+ // Reconnect after 1 second
|
|
|
+ setTimeout(connect, 1000);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function send(event) {
|
|
|
+ event.timestamp = Date.now();
|
|
|
+ if (ws && ws.readyState === WebSocket.OPEN) {
|
|
|
+ ws.send(JSON.stringify(event));
|
|
|
+ } else {
|
|
|
+ eventQueue.push(event);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Auto-capture clicks on interactive elements
|
|
|
+ document.addEventListener('click', (e) => {
|
|
|
+ const target = e.target.closest('button, a, [data-choice], [role="button"], input[type="submit"]');
|
|
|
+ if (!target) return;
|
|
|
+
|
|
|
+ // Don't capture regular link navigation
|
|
|
+ if (target.tagName === 'A' && !target.dataset.choice) return;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ send({
|
|
|
+ type: 'click',
|
|
|
+ text: target.textContent.trim(),
|
|
|
+ choice: target.dataset.choice || null,
|
|
|
+ id: target.id || null,
|
|
|
+ className: target.className || null
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // Auto-capture form submissions
|
|
|
+ document.addEventListener('submit', (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ const form = e.target;
|
|
|
+ const formData = new FormData(form);
|
|
|
+ const data = {};
|
|
|
+ formData.forEach((value, key) => { data[key] = value; });
|
|
|
+
|
|
|
+ send({
|
|
|
+ type: 'submit',
|
|
|
+ formId: form.id || null,
|
|
|
+ formName: form.name || null,
|
|
|
+ data: data
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // Auto-capture input changes (debounced)
|
|
|
+ let inputTimeout = null;
|
|
|
+ document.addEventListener('input', (e) => {
|
|
|
+ const target = e.target;
|
|
|
+ if (!target.matches('input, textarea, select')) return;
|
|
|
+
|
|
|
+ clearTimeout(inputTimeout);
|
|
|
+ inputTimeout = setTimeout(() => {
|
|
|
+ send({
|
|
|
+ type: 'input',
|
|
|
+ name: target.name || null,
|
|
|
+ id: target.id || null,
|
|
|
+ value: target.value,
|
|
|
+ inputType: target.type || target.tagName.toLowerCase()
|
|
|
+ });
|
|
|
+ }, 500); // 500ms debounce
|
|
|
+ });
|
|
|
+
|
|
|
+ // Expose for explicit use if needed
|
|
|
+ window.brainstorm = {
|
|
|
+ send: send,
|
|
|
+ choice: (value, metadata = {}) => send({ type: 'choice', value, ...metadata })
|
|
|
+ };
|
|
|
+
|
|
|
+ connect();
|
|
|
+})();
|
|
|
+```
|
|
|
+
|
|
|
+**Step 2: Verify helper.js is syntactically valid**
|
|
|
+
|
|
|
+Run: `node -c lib/brainstorm-server/helper.js`
|
|
|
+Expected: No syntax errors
|
|
|
+
|
|
|
+**Step 3: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add lib/brainstorm-server/helper.js
|
|
|
+git commit -m "feat: add browser helper library for event capture"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 3: Write Tests for the Server
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `tests/brainstorm-server/server.test.js`
|
|
|
+- Create: `tests/brainstorm-server/package.json`
|
|
|
+
|
|
|
+**Step 1: Create test package.json**
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "name": "brainstorm-server-tests",
|
|
|
+ "version": "1.0.0",
|
|
|
+ "scripts": {
|
|
|
+ "test": "node server.test.js"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**Step 2: Write server tests**
|
|
|
+
|
|
|
+```javascript
|
|
|
+const { spawn } = require('child_process');
|
|
|
+const http = require('http');
|
|
|
+const WebSocket = require('ws');
|
|
|
+const fs = require('fs');
|
|
|
+const path = require('path');
|
|
|
+const assert = require('assert');
|
|
|
+
|
|
|
+const SERVER_PATH = path.join(__dirname, '../../lib/brainstorm-server/index.js');
|
|
|
+const TEST_PORT = 3334;
|
|
|
+const TEST_SCREEN = '/tmp/brainstorm-test/screen.html';
|
|
|
+
|
|
|
+// Clean up test directory
|
|
|
+function cleanup() {
|
|
|
+ if (fs.existsSync(path.dirname(TEST_SCREEN))) {
|
|
|
+ fs.rmSync(path.dirname(TEST_SCREEN), { recursive: true });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function sleep(ms) {
|
|
|
+ return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
+}
|
|
|
+
|
|
|
+async function fetch(url) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ http.get(url, (res) => {
|
|
|
+ let data = '';
|
|
|
+ res.on('data', chunk => data += chunk);
|
|
|
+ res.on('end', () => resolve({ status: res.statusCode, body: data }));
|
|
|
+ }).on('error', reject);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+async function runTests() {
|
|
|
+ cleanup();
|
|
|
+
|
|
|
+ // Start server
|
|
|
+ const server = spawn('node', [SERVER_PATH], {
|
|
|
+ env: { ...process.env, BRAINSTORM_PORT: TEST_PORT, BRAINSTORM_SCREEN: TEST_SCREEN }
|
|
|
+ });
|
|
|
+
|
|
|
+ let stdout = '';
|
|
|
+ server.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
|
+ server.stderr.on('data', (data) => { console.error('Server stderr:', data.toString()); });
|
|
|
+
|
|
|
+ await sleep(1000); // Wait for server to start
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Test 1: Server starts and outputs JSON
|
|
|
+ console.log('Test 1: Server startup message');
|
|
|
+ assert(stdout.includes('server-started'), 'Should output server-started');
|
|
|
+ assert(stdout.includes(TEST_PORT.toString()), 'Should include port');
|
|
|
+ console.log(' PASS');
|
|
|
+
|
|
|
+ // Test 2: GET / returns HTML with helper injected
|
|
|
+ console.log('Test 2: Serves HTML with helper injected');
|
|
|
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
|
|
+ assert.strictEqual(res.status, 200);
|
|
|
+ assert(res.body.includes('brainstorm'), 'Should include brainstorm content');
|
|
|
+ assert(res.body.includes('WebSocket'), 'Should have helper.js injected');
|
|
|
+ console.log(' PASS');
|
|
|
+
|
|
|
+ // Test 3: WebSocket connection and event relay
|
|
|
+ console.log('Test 3: WebSocket relays events to stdout');
|
|
|
+ stdout = ''; // Reset stdout capture
|
|
|
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
|
|
+ await new Promise(resolve => ws.on('open', resolve));
|
|
|
+
|
|
|
+ ws.send(JSON.stringify({ type: 'click', text: 'Test Button' }));
|
|
|
+ await sleep(100);
|
|
|
+
|
|
|
+ assert(stdout.includes('user-event'), 'Should relay user events');
|
|
|
+ assert(stdout.includes('Test Button'), 'Should include event data');
|
|
|
+ ws.close();
|
|
|
+ console.log(' PASS');
|
|
|
+
|
|
|
+ // Test 4: File change triggers reload notification
|
|
|
+ console.log('Test 4: File change notifies browsers');
|
|
|
+ const ws2 = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
|
|
+ await new Promise(resolve => ws2.on('open', resolve));
|
|
|
+
|
|
|
+ let gotReload = false;
|
|
|
+ ws2.on('message', (data) => {
|
|
|
+ const msg = JSON.parse(data.toString());
|
|
|
+ if (msg.type === 'reload') gotReload = true;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Modify the screen file
|
|
|
+ fs.writeFileSync(TEST_SCREEN, '<html><body>Updated</body></html>');
|
|
|
+ await sleep(500);
|
|
|
+
|
|
|
+ assert(gotReload, 'Should send reload message on file change');
|
|
|
+ ws2.close();
|
|
|
+ console.log(' PASS');
|
|
|
+
|
|
|
+ console.log('\nAll tests passed!');
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ server.kill();
|
|
|
+ cleanup();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+runTests().catch(err => {
|
|
|
+ console.error('Test failed:', err);
|
|
|
+ process.exit(1);
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**Step 3: Run tests**
|
|
|
+
|
|
|
+Run: `cd tests/brainstorm-server && npm install ws && node server.test.js`
|
|
|
+Expected: All tests pass
|
|
|
+
|
|
|
+**Step 4: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add tests/brainstorm-server/
|
|
|
+git commit -m "test: add brainstorm server integration tests"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 4: Add Visual Companion to Brainstorming Skill
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `skills/brainstorming/SKILL.md`
|
|
|
+- Create: `skills/brainstorming/visual-companion.md` (supporting doc)
|
|
|
+
|
|
|
+**Step 1: Create the supporting documentation**
|
|
|
+
|
|
|
+Create `skills/brainstorming/visual-companion.md`:
|
|
|
+
|
|
|
+```markdown
|
|
|
+# Visual Companion Reference
|
|
|
+
|
|
|
+## Starting the Server
|
|
|
+
|
|
|
+Run as a background job:
|
|
|
+
|
|
|
+```bash
|
|
|
+node ${PLUGIN_ROOT}/lib/brainstorm-server/index.js
|
|
|
+```
|
|
|
+
|
|
|
+Tell the user: "I've started a visual companion at http://localhost:3333 - open it in a browser."
|
|
|
+
|
|
|
+## Pushing Screens
|
|
|
+
|
|
|
+Write HTML to `/tmp/brainstorm/screen.html`. The server watches this file and auto-refreshes the browser.
|
|
|
+
|
|
|
+## Reading User Responses
|
|
|
+
|
|
|
+Check the background task output for JSON events:
|
|
|
+
|
|
|
+```json
|
|
|
+{"type":"user-event","type":"click","text":"Option A","choice":"optionA","timestamp":1234567890}
|
|
|
+{"type":"user-event","type":"submit","data":{"notes":"My feedback"},"timestamp":1234567891}
|
|
|
+```
|
|
|
+
|
|
|
+Event types:
|
|
|
+- **click**: User clicked button or `data-choice` element
|
|
|
+- **submit**: User submitted form (includes all form data)
|
|
|
+- **input**: User typed in field (debounced 500ms)
|
|
|
+
|
|
|
+## HTML Patterns
|
|
|
+
|
|
|
+### Choice Cards
|
|
|
+
|
|
|
+```html
|
|
|
+<div class="options">
|
|
|
+ <button data-choice="optionA">
|
|
|
+ <h3>Option A</h3>
|
|
|
+ <p>Description</p>
|
|
|
+ </button>
|
|
|
+ <button data-choice="optionB">
|
|
|
+ <h3>Option B</h3>
|
|
|
+ <p>Description</p>
|
|
|
+ </button>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+### Interactive Mockup
|
|
|
+
|
|
|
+```html
|
|
|
+<div class="mockup">
|
|
|
+ <header data-choice="header">App Header</header>
|
|
|
+ <nav data-choice="nav">Navigation</nav>
|
|
|
+ <main data-choice="main">Content</main>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+### Form with Notes
|
|
|
+
|
|
|
+```html
|
|
|
+<form>
|
|
|
+ <label>Priority: <input type="range" name="priority" min="1" max="5"></label>
|
|
|
+ <textarea name="notes" placeholder="Additional thoughts..."></textarea>
|
|
|
+ <button type="submit">Submit</button>
|
|
|
+</form>
|
|
|
+```
|
|
|
+
|
|
|
+### Explicit JavaScript
|
|
|
+
|
|
|
+```html
|
|
|
+<button onclick="brainstorm.choice('custom', {extra: 'data'})">Custom</button>
|
|
|
+```
|
|
|
+```
|
|
|
+
|
|
|
+**Step 2: Add visual companion section to brainstorming skill**
|
|
|
+
|
|
|
+Add after "Key Principles" in `skills/brainstorming/SKILL.md`:
|
|
|
+
|
|
|
+```markdown
|
|
|
+
|
|
|
+## Visual Companion (Optional)
|
|
|
+
|
|
|
+When brainstorming involves visual elements - UI mockups, wireframes, interactive prototypes - use the browser-based visual companion.
|
|
|
+
|
|
|
+**When to use:**
|
|
|
+- Presenting UI/UX options that benefit from visual comparison
|
|
|
+- Showing wireframes or layout options
|
|
|
+- Gathering structured feedback (ratings, forms)
|
|
|
+- Prototyping click interactions
|
|
|
+
|
|
|
+**How it works:**
|
|
|
+1. Start the server as a background job
|
|
|
+2. Tell user to open http://localhost:3333
|
|
|
+3. Write HTML to `/tmp/brainstorm/screen.html` (auto-refreshes)
|
|
|
+4. Check background task output for user interactions
|
|
|
+
|
|
|
+The terminal remains the primary conversation interface. The browser is a visual aid.
|
|
|
+
|
|
|
+**Reference:** See `visual-companion.md` in this skill directory for HTML patterns and API details.
|
|
|
+```
|
|
|
+
|
|
|
+**Step 3: Verify the edits**
|
|
|
+
|
|
|
+Run: `grep -A5 "Visual Companion" skills/brainstorming/SKILL.md`
|
|
|
+Expected: Shows the new section
|
|
|
+
|
|
|
+**Step 4: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add skills/brainstorming/
|
|
|
+git commit -m "feat: add visual companion to brainstorming skill"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 5: Add Server to Plugin Ignore (Optional Cleanup)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Check if `.gitignore` needs node_modules exclusion for lib/brainstorm-server
|
|
|
+
|
|
|
+**Step 1: Check current gitignore**
|
|
|
+
|
|
|
+Run: `cat .gitignore 2>/dev/null || echo "No .gitignore"`
|
|
|
+
|
|
|
+**Step 2: Add node_modules if needed**
|
|
|
+
|
|
|
+If not already present, add:
|
|
|
+```
|
|
|
+lib/brainstorm-server/node_modules/
|
|
|
+```
|
|
|
+
|
|
|
+**Step 3: Commit if changed**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add .gitignore
|
|
|
+git commit -m "chore: ignore brainstorm-server node_modules"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Summary
|
|
|
+
|
|
|
+After completing all tasks:
|
|
|
+
|
|
|
+1. **Server** at `lib/brainstorm-server/` - Node.js server that watches HTML file and relays events
|
|
|
+2. **Helper library** auto-injected - captures clicks, forms, inputs
|
|
|
+3. **Tests** at `tests/brainstorm-server/` - verifies server behavior
|
|
|
+4. **Brainstorming skill** updated with visual companion section and `visual-companion.md` reference doc
|
|
|
+
|
|
|
+**To use:**
|
|
|
+1. Start server as background job: `node lib/brainstorm-server/index.js &`
|
|
|
+2. Tell user to open `http://localhost:3333`
|
|
|
+3. Write HTML to `/tmp/brainstorm/screen.html`
|
|
|
+4. Check task output for user events
|