brainstorm-companion 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,277 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Brainstorm Companion — Compare</title>
6
+ <style>
7
+ :root {
8
+ --bg-primary: #0f1117;
9
+ --bg-secondary: #1a1d2e;
10
+ --bg-tertiary: #252840;
11
+ --accent-primary: #6c63ff;
12
+ --accent-secondary: #ff6584;
13
+ --accent-tertiary: #43e97b;
14
+ --text-primary: #e8eaf6;
15
+ --text-secondary: #9fa8da;
16
+ --text-muted: #5c6bc0;
17
+ --border-color: #2d3561;
18
+ --shadow: 0 4px 20px rgba(0,0,0,0.4);
19
+ --radius: 12px;
20
+ }
21
+
22
+ * { box-sizing: border-box; margin: 0; padding: 0; }
23
+
24
+ html, body {
25
+ height: 100%;
26
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
27
+ background: var(--bg-primary);
28
+ color: var(--text-primary);
29
+ display: flex;
30
+ flex-direction: column;
31
+ overflow: hidden;
32
+ }
33
+
34
+ /* Header */
35
+ .header {
36
+ background: var(--bg-secondary);
37
+ border-bottom: 1px solid var(--border-color);
38
+ padding: 0.75rem 1.5rem;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ flex-shrink: 0;
43
+ z-index: 100;
44
+ }
45
+
46
+ .header h1 {
47
+ font-size: 1.1rem;
48
+ font-weight: 600;
49
+ color: var(--text-primary);
50
+ letter-spacing: -0.02em;
51
+ }
52
+
53
+ .view-controls {
54
+ display: flex;
55
+ gap: 0.5rem;
56
+ }
57
+
58
+ .view-btn {
59
+ background: var(--bg-tertiary);
60
+ border: 1px solid var(--border-color);
61
+ border-radius: 6px;
62
+ color: var(--text-secondary);
63
+ cursor: pointer;
64
+ font-size: 0.8rem;
65
+ padding: 0.3rem 0.75rem;
66
+ transition: all 0.15s ease;
67
+ }
68
+
69
+ .view-btn:hover {
70
+ border-color: var(--accent-primary);
71
+ color: var(--text-primary);
72
+ }
73
+
74
+ .view-btn.active {
75
+ background: rgba(108, 99, 255, 0.15);
76
+ border-color: var(--accent-primary);
77
+ color: var(--accent-primary);
78
+ }
79
+
80
+ .status {
81
+ font-size: 0.75rem;
82
+ color: var(--accent-tertiary);
83
+ background: rgba(67, 233, 123, 0.1);
84
+ border: 1px solid rgba(67, 233, 123, 0.2);
85
+ padding: 0.25rem 0.75rem;
86
+ border-radius: 20px;
87
+ }
88
+
89
+ /* Tab bar */
90
+ .tab-bar {
91
+ background: var(--bg-secondary);
92
+ border-bottom: 1px solid var(--border-color);
93
+ display: flex;
94
+ gap: 0;
95
+ flex-shrink: 0;
96
+ overflow-x: auto;
97
+ }
98
+
99
+ .tab {
100
+ background: transparent;
101
+ border: none;
102
+ border-bottom: 3px solid transparent;
103
+ color: var(--text-secondary);
104
+ cursor: pointer;
105
+ font-size: 0.875rem;
106
+ font-weight: 500;
107
+ padding: 0.75rem 1.5rem;
108
+ transition: all 0.15s ease;
109
+ white-space: nowrap;
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 0.5rem;
113
+ }
114
+
115
+ .tab:hover {
116
+ color: var(--text-primary);
117
+ background: rgba(108, 99, 255, 0.05);
118
+ }
119
+
120
+ .tab.active {
121
+ border-bottom-color: var(--accent-primary);
122
+ color: var(--accent-primary);
123
+ }
124
+
125
+ .tab .slot-letter {
126
+ font-weight: 700;
127
+ font-size: 0.9rem;
128
+ }
129
+
130
+ .tab .slot-label {
131
+ font-size: 0.8rem;
132
+ color: var(--text-muted);
133
+ }
134
+
135
+ .tab.active .slot-label {
136
+ color: var(--text-secondary);
137
+ }
138
+
139
+ /* Panels container */
140
+ .panels {
141
+ flex: 1;
142
+ display: flex;
143
+ overflow: hidden;
144
+ min-height: 0;
145
+ }
146
+
147
+ .panel {
148
+ flex: 1;
149
+ display: flex;
150
+ flex-direction: column;
151
+ border-right: 1px solid var(--border-color);
152
+ min-width: 0;
153
+ }
154
+
155
+ .panel:last-child {
156
+ border-right: none;
157
+ }
158
+
159
+ .panel.hidden {
160
+ display: none;
161
+ }
162
+
163
+ .panel-header {
164
+ background: var(--bg-tertiary);
165
+ border-bottom: 1px solid var(--border-color);
166
+ padding: 0.4rem 1rem;
167
+ font-size: 0.75rem;
168
+ font-weight: 700;
169
+ color: var(--text-muted);
170
+ text-transform: uppercase;
171
+ letter-spacing: 0.08em;
172
+ flex-shrink: 0;
173
+ }
174
+
175
+ .panel iframe {
176
+ border: none;
177
+ width: 100%;
178
+ flex: 1;
179
+ display: block;
180
+ min-height: 0;
181
+ }
182
+
183
+ /* Preference bar */
184
+ .preference-bar {
185
+ background: var(--bg-secondary);
186
+ border-top: 1px solid var(--border-color);
187
+ padding: 0.6rem 1.5rem;
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 0.75rem;
191
+ flex-shrink: 0;
192
+ font-size: 0.875rem;
193
+ color: var(--text-secondary);
194
+ }
195
+
196
+ .preference-bar span {
197
+ font-weight: 600;
198
+ color: var(--text-muted);
199
+ font-size: 0.8rem;
200
+ text-transform: uppercase;
201
+ letter-spacing: 0.05em;
202
+ }
203
+
204
+ .pref-btn {
205
+ background: var(--bg-tertiary);
206
+ border: 1px solid var(--border-color);
207
+ border-radius: 8px;
208
+ color: var(--text-secondary);
209
+ cursor: pointer;
210
+ font-size: 0.8rem;
211
+ font-weight: 600;
212
+ padding: 0.35rem 1rem;
213
+ transition: all 0.15s ease;
214
+ }
215
+
216
+ .pref-btn:hover {
217
+ border-color: var(--accent-tertiary);
218
+ color: var(--accent-tertiary);
219
+ }
220
+
221
+ .pref-btn.selected {
222
+ background: rgba(67, 233, 123, 0.12);
223
+ border-color: var(--accent-tertiary);
224
+ color: var(--accent-tertiary);
225
+ }
226
+
227
+ .pref-result {
228
+ margin-left: auto;
229
+ font-size: 0.8rem;
230
+ color: var(--accent-tertiary);
231
+ font-weight: 500;
232
+ }
233
+
234
+ .keyboard-hint {
235
+ margin-left: auto;
236
+ font-size: 0.75rem;
237
+ color: var(--text-muted);
238
+ }
239
+
240
+ kbd {
241
+ background: var(--bg-tertiary);
242
+ border: 1px solid var(--border-color);
243
+ border-radius: 4px;
244
+ font-family: inherit;
245
+ font-size: 0.7rem;
246
+ padding: 0.1rem 0.35rem;
247
+ }
248
+ </style>
249
+ </head>
250
+ <body>
251
+ <div class="header">
252
+ <h1>Brainstorm Companion</h1>
253
+ <div class="view-controls">
254
+ <button class="view-btn active" data-view="side-by-side">Side by Side</button>
255
+ <button class="view-btn" data-view="single">Single</button>
256
+ </div>
257
+ <div class="status" id="status">Connected</div>
258
+ </div>
259
+
260
+ <div class="tab-bar" id="tab-bar">
261
+ <!-- Tabs populated by JS -->
262
+ </div>
263
+
264
+ <div class="panels" id="panels">
265
+ <!-- Iframes populated by JS -->
266
+ </div>
267
+
268
+ <div class="preference-bar" id="preference-bar">
269
+ <span>Preferred:</span>
270
+ <!-- Preference buttons populated by JS -->
271
+ <div class="keyboard-hint">
272
+ <kbd>1</kbd><kbd>2</kbd><kbd>3</kbd> switch slots &nbsp;
273
+ <kbd>Tab</kbd> toggle view
274
+ </div>
275
+ </div>
276
+ </body>
277
+ </html>
@@ -0,0 +1,283 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Brainstorm Companion</title>
6
+ <style>
7
+ :root {
8
+ --bg-primary: #0f1117;
9
+ --bg-secondary: #1a1d2e;
10
+ --bg-tertiary: #252840;
11
+ --accent-primary: #6c63ff;
12
+ --accent-secondary: #ff6584;
13
+ --accent-tertiary: #43e97b;
14
+ --text-primary: #e8eaf6;
15
+ --text-secondary: #9fa8da;
16
+ --text-muted: #5c6bc0;
17
+ --border-color: #2d3561;
18
+ --shadow: 0 4px 20px rgba(0,0,0,0.4);
19
+ --radius: 12px;
20
+ }
21
+
22
+ * { box-sizing: border-box; margin: 0; padding: 0; }
23
+
24
+ body {
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
26
+ background: var(--bg-primary);
27
+ color: var(--text-primary);
28
+ min-height: 100vh;
29
+ display: flex;
30
+ flex-direction: column;
31
+ }
32
+
33
+ .header {
34
+ background: var(--bg-secondary);
35
+ border-bottom: 1px solid var(--border-color);
36
+ padding: 1rem 2rem;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: space-between;
40
+ position: sticky;
41
+ top: 0;
42
+ z-index: 100;
43
+ }
44
+
45
+ .header h1 {
46
+ font-size: 1.25rem;
47
+ font-weight: 600;
48
+ color: var(--text-primary);
49
+ letter-spacing: -0.02em;
50
+ }
51
+
52
+ .status {
53
+ font-size: 0.75rem;
54
+ color: var(--accent-tertiary);
55
+ background: rgba(67, 233, 123, 0.1);
56
+ border: 1px solid rgba(67, 233, 123, 0.2);
57
+ padding: 0.25rem 0.75rem;
58
+ border-radius: 20px;
59
+ }
60
+
61
+ .main {
62
+ flex: 1;
63
+ padding: 2rem;
64
+ max-width: 900px;
65
+ margin: 0 auto;
66
+ width: 100%;
67
+ }
68
+
69
+ .indicator-bar {
70
+ position: fixed;
71
+ bottom: 0;
72
+ left: 0;
73
+ right: 0;
74
+ background: var(--bg-secondary);
75
+ border-top: 1px solid var(--border-color);
76
+ padding: 0.75rem 2rem;
77
+ text-align: center;
78
+ font-size: 0.875rem;
79
+ color: var(--text-secondary);
80
+ z-index: 100;
81
+ }
82
+
83
+ .selected-text {
84
+ color: var(--accent-tertiary);
85
+ font-weight: 600;
86
+ }
87
+
88
+ h2 {
89
+ font-size: 1.5rem;
90
+ font-weight: 700;
91
+ color: var(--text-primary);
92
+ margin-bottom: 0.5rem;
93
+ letter-spacing: -0.02em;
94
+ }
95
+
96
+ h3 {
97
+ font-size: 1rem;
98
+ font-weight: 600;
99
+ color: var(--text-primary);
100
+ margin-bottom: 0.25rem;
101
+ }
102
+
103
+ p {
104
+ color: var(--text-secondary);
105
+ line-height: 1.6;
106
+ margin-bottom: 1rem;
107
+ }
108
+
109
+ .section {
110
+ margin-bottom: 2rem;
111
+ }
112
+
113
+ .section-label {
114
+ font-size: 0.75rem;
115
+ font-weight: 700;
116
+ text-transform: uppercase;
117
+ letter-spacing: 0.1em;
118
+ color: var(--text-muted);
119
+ margin-bottom: 1rem;
120
+ }
121
+
122
+ .options {
123
+ display: grid;
124
+ gap: 0.75rem;
125
+ }
126
+
127
+ .options.grid-2 { grid-template-columns: repeat(2, 1fr); }
128
+ .options.grid-3 { grid-template-columns: repeat(3, 1fr); }
129
+
130
+ .option {
131
+ background: var(--bg-secondary);
132
+ border: 1px solid var(--border-color);
133
+ border-radius: var(--radius);
134
+ padding: 1.25rem;
135
+ cursor: pointer;
136
+ transition: all 0.15s ease;
137
+ position: relative;
138
+ overflow: hidden;
139
+ }
140
+
141
+ .option:hover {
142
+ border-color: var(--accent-primary);
143
+ background: rgba(108, 99, 255, 0.05);
144
+ transform: translateY(-1px);
145
+ box-shadow: var(--shadow);
146
+ }
147
+
148
+ .option.selected {
149
+ border-color: var(--accent-primary);
150
+ background: rgba(108, 99, 255, 0.1);
151
+ }
152
+
153
+ .option.selected::after {
154
+ content: '✓';
155
+ position: absolute;
156
+ top: 0.75rem;
157
+ right: 0.75rem;
158
+ color: var(--accent-primary);
159
+ font-size: 1rem;
160
+ font-weight: 700;
161
+ }
162
+
163
+ .option .content p {
164
+ font-size: 0.875rem;
165
+ color: var(--text-secondary);
166
+ margin: 0;
167
+ }
168
+
169
+ .cards {
170
+ display: grid;
171
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
172
+ gap: 1rem;
173
+ }
174
+
175
+ .card {
176
+ background: var(--bg-secondary);
177
+ border: 1px solid var(--border-color);
178
+ border-radius: var(--radius);
179
+ overflow: hidden;
180
+ cursor: pointer;
181
+ transition: all 0.15s ease;
182
+ }
183
+
184
+ .card:hover {
185
+ border-color: var(--accent-primary);
186
+ transform: translateY(-2px);
187
+ box-shadow: var(--shadow);
188
+ }
189
+
190
+ .card.selected {
191
+ border-color: var(--accent-primary);
192
+ background: rgba(108, 99, 255, 0.1);
193
+ }
194
+
195
+ .card-header {
196
+ padding: 1rem;
197
+ background: var(--bg-tertiary);
198
+ border-bottom: 1px solid var(--border-color);
199
+ }
200
+
201
+ .card-body {
202
+ padding: 1rem;
203
+ }
204
+
205
+ .card-body p {
206
+ font-size: 0.875rem;
207
+ color: var(--text-secondary);
208
+ margin: 0;
209
+ }
210
+
211
+ .tag {
212
+ display: inline-block;
213
+ font-size: 0.7rem;
214
+ font-weight: 600;
215
+ text-transform: uppercase;
216
+ letter-spacing: 0.05em;
217
+ padding: 0.2rem 0.6rem;
218
+ border-radius: 4px;
219
+ margin-bottom: 0.5rem;
220
+ }
221
+
222
+ .tag-purple { background: rgba(108, 99, 255, 0.2); color: #a89cff; }
223
+ .tag-pink { background: rgba(255, 101, 132, 0.2); color: #ff8fa8; }
224
+ .tag-green { background: rgba(67, 233, 123, 0.2); color: #6effa0; }
225
+ .tag-blue { background: rgba(64, 196, 255, 0.2); color: #7dd8ff; }
226
+
227
+ .progress-bar {
228
+ background: var(--bg-tertiary);
229
+ border-radius: 4px;
230
+ height: 6px;
231
+ margin-top: 0.5rem;
232
+ overflow: hidden;
233
+ }
234
+
235
+ .progress-fill {
236
+ height: 100%;
237
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
238
+ border-radius: 4px;
239
+ transition: width 0.3s ease;
240
+ }
241
+
242
+ .info-box {
243
+ background: rgba(108, 99, 255, 0.08);
244
+ border: 1px solid rgba(108, 99, 255, 0.2);
245
+ border-radius: var(--radius);
246
+ padding: 1.25rem;
247
+ margin-bottom: 1.5rem;
248
+ }
249
+
250
+ .info-box p {
251
+ margin: 0;
252
+ font-size: 0.9rem;
253
+ }
254
+
255
+ ul, ol {
256
+ padding-left: 1.5rem;
257
+ color: var(--text-secondary);
258
+ line-height: 1.8;
259
+ }
260
+
261
+ li { margin-bottom: 0.25rem; }
262
+
263
+ pre { background: var(--bg-tertiary); border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
264
+ code { font-family: 'SF Mono', Menlo, monospace; font-size: 0.9em; }
265
+ pre code { display: block; }
266
+ .mermaid { background: var(--bg-secondary); border-radius: 8px; padding: 1rem; margin: 1rem 0; }
267
+ </style>
268
+ </head>
269
+ <body>
270
+ <div class="header">
271
+ <h1>Brainstorm Companion</h1>
272
+ <div class="status">Connected</div>
273
+ </div>
274
+ <div class="main">
275
+ <div id="claude-content">
276
+ <!-- CONTENT -->
277
+ </div>
278
+ </div>
279
+ <div class="indicator-bar">
280
+ <span id="indicator-text">Click an option above, then return to the terminal</span>
281
+ </div>
282
+ </body>
283
+ </html>
@@ -0,0 +1,78 @@
1
+ (function() {
2
+ const WS_URL = 'ws://' + window.location.host;
3
+ let ws = null;
4
+ let eventQueue = [];
5
+
6
+ function connect() {
7
+ ws = new WebSocket(WS_URL);
8
+ ws.onopen = () => {
9
+ eventQueue.forEach(e => ws.send(JSON.stringify(e)));
10
+ eventQueue = [];
11
+ };
12
+ ws.onmessage = (msg) => {
13
+ const data = JSON.parse(msg.data);
14
+ if (data.type === 'reload') {
15
+ window.location.reload();
16
+ }
17
+ };
18
+ ws.onclose = () => {
19
+ setTimeout(connect, 1000);
20
+ };
21
+ }
22
+
23
+ function sendEvent(event) {
24
+ event.timestamp = Date.now();
25
+ if (ws && ws.readyState === WebSocket.OPEN) {
26
+ ws.send(JSON.stringify(event));
27
+ } else {
28
+ eventQueue.push(event);
29
+ }
30
+ }
31
+
32
+ document.addEventListener('click', (e) => {
33
+ const target = e.target.closest('[data-choice]');
34
+ if (!target) return;
35
+ sendEvent({
36
+ type: 'click',
37
+ text: target.textContent.trim(),
38
+ choice: target.dataset.choice,
39
+ id: target.id || null
40
+ });
41
+ setTimeout(() => {
42
+ const indicator = document.getElementById('indicator-text');
43
+ if (!indicator) return;
44
+ const container = target.closest('.options') || target.closest('.cards');
45
+ const selected = container ? container.querySelectorAll('.selected') : [];
46
+ if (selected.length === 0) {
47
+ indicator.textContent = 'Click an option above, then return to the terminal';
48
+ } else if (selected.length === 1) {
49
+ const label = selected[0].querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || selected[0].dataset.choice;
50
+ indicator.innerHTML = '<span class="selected-text">' + label + ' selected</span> — return to terminal to continue';
51
+ } else {
52
+ indicator.innerHTML = '<span class="selected-text">' + selected.length + ' selected</span> — return to terminal to continue';
53
+ }
54
+ }, 0);
55
+ });
56
+
57
+ window.selectedChoice = null;
58
+ window.toggleSelect = function(el) {
59
+ const container = el.closest('.options') || el.closest('.cards');
60
+ const multi = container && container.dataset.multiselect !== undefined;
61
+ if (container && !multi) {
62
+ container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected'));
63
+ }
64
+ if (multi) {
65
+ el.classList.toggle('selected');
66
+ } else {
67
+ el.classList.add('selected');
68
+ }
69
+ window.selectedChoice = el.dataset.choice;
70
+ };
71
+
72
+ window.brainstorm = {
73
+ send: sendEvent,
74
+ choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata })
75
+ };
76
+
77
+ connect();
78
+ })();
@@ -0,0 +1,8 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><meta charset="utf-8"><title>Brainstorm Companion</title>
4
+ <style>body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
5
+ h1 { color: #333; } p { color: #666; }</style>
6
+ </head>
7
+ <body><h1>Brainstorm Companion</h1>
8
+ <p>Waiting for content to be pushed...</p></body></html>
@@ -0,0 +1,69 @@
1
+ const crypto = require('crypto');
2
+
3
+ const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
4
+ const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
5
+
6
+ function computeAcceptKey(clientKey) {
7
+ return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
8
+ }
9
+
10
+ function encodeFrame(opcode, payload) {
11
+ const fin = 0x80;
12
+ const len = payload.length;
13
+ let header;
14
+
15
+ if (len < 126) {
16
+ header = Buffer.alloc(2);
17
+ header[0] = fin | opcode;
18
+ header[1] = len;
19
+ } else if (len < 65536) {
20
+ header = Buffer.alloc(4);
21
+ header[0] = fin | opcode;
22
+ header[1] = 126;
23
+ header.writeUInt16BE(len, 2);
24
+ } else {
25
+ header = Buffer.alloc(10);
26
+ header[0] = fin | opcode;
27
+ header[1] = 127;
28
+ header.writeBigUInt64BE(BigInt(len), 2);
29
+ }
30
+
31
+ return Buffer.concat([header, payload]);
32
+ }
33
+
34
+ function decodeFrame(buffer) {
35
+ if (buffer.length < 2) return null;
36
+
37
+ const secondByte = buffer[1];
38
+ const opcode = buffer[0] & 0x0F;
39
+ const masked = (secondByte & 0x80) !== 0;
40
+ let payloadLen = secondByte & 0x7F;
41
+ let offset = 2;
42
+
43
+ if (!masked) throw new Error('Client frames must be masked');
44
+
45
+ if (payloadLen === 126) {
46
+ if (buffer.length < 4) return null;
47
+ payloadLen = buffer.readUInt16BE(2);
48
+ offset = 4;
49
+ } else if (payloadLen === 127) {
50
+ if (buffer.length < 10) return null;
51
+ payloadLen = Number(buffer.readBigUInt64BE(2));
52
+ offset = 10;
53
+ }
54
+
55
+ const maskOffset = offset;
56
+ const dataOffset = offset + 4;
57
+ const totalLen = dataOffset + payloadLen;
58
+ if (buffer.length < totalLen) return null;
59
+
60
+ const mask = buffer.slice(maskOffset, dataOffset);
61
+ const data = Buffer.alloc(payloadLen);
62
+ for (let i = 0; i < payloadLen; i++) {
63
+ data[i] = buffer[dataOffset + i] ^ mask[i % 4];
64
+ }
65
+
66
+ return { opcode, payload: data, bytesConsumed: totalLen };
67
+ }
68
+
69
+ module.exports = { OPCODES, WS_MAGIC, computeAcceptKey, encodeFrame, decodeFrame };