watercooler 0.0.1

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,516 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Watercooler Village</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ overflow: hidden;
19
+ }
20
+
21
+ #canvas-container {
22
+ position: fixed;
23
+ top: 0;
24
+ left: 0;
25
+ width: 100%;
26
+ height: 100%;
27
+ z-index: 1;
28
+ }
29
+
30
+ /* Collapsible Send Panel - Top Left */
31
+ .send-panel {
32
+ position: fixed;
33
+ top: 20px;
34
+ left: 20px;
35
+ width: 320px;
36
+ background: rgba(255, 255, 255, 0.15);
37
+ backdrop-filter: blur(20px);
38
+ border-radius: 16px;
39
+ border: 1px solid rgba(255, 255, 255, 0.2);
40
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
41
+ z-index: 100;
42
+ overflow: hidden;
43
+ transition: all 0.3s ease;
44
+ }
45
+
46
+ .send-panel.collapsed {
47
+ width: auto;
48
+ min-width: 180px;
49
+ }
50
+
51
+ .send-panel.collapsed .send-form {
52
+ display: none;
53
+ }
54
+
55
+ .send-header {
56
+ padding: 12px 16px;
57
+ display: flex;
58
+ justify-content: space-between;
59
+ align-items: center;
60
+ cursor: pointer;
61
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
62
+ }
63
+
64
+ .send-header:hover {
65
+ background: rgba(255, 255, 255, 0.05);
66
+ }
67
+
68
+ .send-header h2 {
69
+ font-size: 0.9rem;
70
+ color: white;
71
+ font-weight: 600;
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ }
76
+
77
+ .collapse-btn {
78
+ background: none;
79
+ border: none;
80
+ color: white;
81
+ font-size: 1.2rem;
82
+ cursor: pointer;
83
+ opacity: 0.7;
84
+ transition: opacity 0.2s;
85
+ }
86
+
87
+ .collapse-btn:hover {
88
+ opacity: 1;
89
+ }
90
+
91
+ .send-form {
92
+ padding: 16px;
93
+ }
94
+
95
+ .recipient-select {
96
+ width: 100%;
97
+ padding: 10px 12px;
98
+ margin-bottom: 10px;
99
+ border-radius: 10px;
100
+ border: 1px solid rgba(255, 255, 255, 0.2);
101
+ background: rgba(0, 0, 0, 0.2);
102
+ color: white;
103
+ font-size: 0.9rem;
104
+ cursor: pointer;
105
+ outline: none;
106
+ }
107
+
108
+ .recipient-select option {
109
+ background: #333;
110
+ color: white;
111
+ }
112
+
113
+ .message-input {
114
+ width: 100%;
115
+ padding: 10px 12px;
116
+ margin-bottom: 10px;
117
+ border-radius: 10px;
118
+ border: 1px solid rgba(255, 255, 255, 0.2);
119
+ background: rgba(0, 0, 0, 0.2);
120
+ color: white;
121
+ font-size: 0.9rem;
122
+ min-height: 60px;
123
+ resize: vertical;
124
+ outline: none;
125
+ font-family: inherit;
126
+ }
127
+
128
+ .message-input::placeholder {
129
+ color: rgba(255, 255, 255, 0.5);
130
+ }
131
+
132
+ .send-btn {
133
+ width: 100%;
134
+ padding: 10px;
135
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
136
+ color: white;
137
+ border: none;
138
+ border-radius: 10px;
139
+ font-size: 0.9rem;
140
+ font-weight: 600;
141
+ cursor: pointer;
142
+ transition: all 0.3s ease;
143
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
144
+ }
145
+
146
+ .send-btn:hover {
147
+ transform: translateY(-2px);
148
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
149
+ }
150
+
151
+ /* Messages Panel - Slide from Right */
152
+ .messages-panel {
153
+ position: fixed;
154
+ top: 20px;
155
+ right: -400px;
156
+ width: 380px;
157
+ max-height: calc(100vh - 40px);
158
+ background: rgba(255, 255, 255, 0.15);
159
+ backdrop-filter: blur(20px);
160
+ border-radius: 16px;
161
+ border: 1px solid rgba(255, 255, 255, 0.2);
162
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
163
+ z-index: 100;
164
+ overflow: hidden;
165
+ display: flex;
166
+ flex-direction: column;
167
+ transition: right 0.3s ease;
168
+ }
169
+
170
+ .messages-panel.open {
171
+ right: 20px;
172
+ }
173
+
174
+ .messages-header {
175
+ padding: 16px;
176
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
177
+ display: flex;
178
+ justify-content: space-between;
179
+ align-items: center;
180
+ }
181
+
182
+ .messages-header h2 {
183
+ font-size: 1rem;
184
+ color: white;
185
+ font-weight: 600;
186
+ }
187
+
188
+ .close-btn {
189
+ background: none;
190
+ border: none;
191
+ color: white;
192
+ font-size: 1.5rem;
193
+ cursor: pointer;
194
+ opacity: 0.7;
195
+ transition: opacity 0.2s;
196
+ }
197
+
198
+ .close-btn:hover {
199
+ opacity: 1;
200
+ }
201
+
202
+ .messages-container {
203
+ flex: 1;
204
+ overflow-y: auto;
205
+ padding: 16px;
206
+ }
207
+
208
+ .message-card {
209
+ background: rgba(255, 255, 255, 0.1);
210
+ border-radius: 12px;
211
+ padding: 12px;
212
+ margin-bottom: 10px;
213
+ border-left: 3px solid #4CAF50;
214
+ transition: all 0.3s ease;
215
+ cursor: pointer;
216
+ }
217
+
218
+ .message-card:hover {
219
+ background: rgba(255, 255, 255, 0.15);
220
+ }
221
+
222
+ .message-card.unread {
223
+ border-left-color: #ff6b6b;
224
+ background: rgba(255, 107, 107, 0.15);
225
+ }
226
+
227
+ .message-header {
228
+ display: flex;
229
+ justify-content: space-between;
230
+ align-items: center;
231
+ margin-bottom: 6px;
232
+ }
233
+
234
+ .message-sender {
235
+ font-weight: 600;
236
+ color: white;
237
+ font-size: 0.85rem;
238
+ }
239
+
240
+ .message-time {
241
+ font-size: 0.7rem;
242
+ color: rgba(255, 255, 255, 0.6);
243
+ }
244
+
245
+ .message-text {
246
+ color: rgba(255, 255, 255, 0.9);
247
+ font-size: 0.85rem;
248
+ line-height: 1.4;
249
+ }
250
+
251
+ /* Toggle Messages Button */
252
+ .toggle-messages-btn {
253
+ position: fixed;
254
+ top: 20px;
255
+ right: 20px;
256
+ padding: 12px 20px;
257
+ background: rgba(255, 255, 255, 0.15);
258
+ backdrop-filter: blur(20px);
259
+ border: 1px solid rgba(255, 255, 255, 0.2);
260
+ border-radius: 12px;
261
+ color: white;
262
+ font-size: 0.9rem;
263
+ font-weight: 600;
264
+ cursor: pointer;
265
+ z-index: 99;
266
+ transition: all 0.3s ease;
267
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
268
+ }
269
+
270
+ .toggle-messages-btn:hover {
271
+ background: rgba(255, 255, 255, 0.25);
272
+ }
273
+
274
+ .toggle-messages-btn .badge {
275
+ background: #ff6b6b;
276
+ color: white;
277
+ padding: 2px 8px;
278
+ border-radius: 12px;
279
+ font-size: 0.75rem;
280
+ margin-left: 8px;
281
+ }
282
+
283
+ /* Toast notification */
284
+ .toast {
285
+ position: fixed;
286
+ bottom: 30px;
287
+ right: 30px;
288
+ background: rgba(76, 175, 80, 0.9);
289
+ backdrop-filter: blur(10px);
290
+ color: white;
291
+ padding: 14px 20px;
292
+ border-radius: 10px;
293
+ font-weight: 600;
294
+ opacity: 0;
295
+ transform: translateY(20px);
296
+ transition: all 0.3s ease;
297
+ z-index: 200;
298
+ }
299
+
300
+ .toast.show {
301
+ opacity: 1;
302
+ transform: translateY(0);
303
+ }
304
+
305
+ /* Empty state */
306
+ .empty-state {
307
+ text-align: center;
308
+ padding: 40px 20px;
309
+ color: rgba(255, 255, 255, 0.6);
310
+ }
311
+
312
+ /* Scrollbar */
313
+ .messages-container::-webkit-scrollbar {
314
+ width: 6px;
315
+ }
316
+
317
+ .messages-container::-webkit-scrollbar-track {
318
+ background: rgba(255, 255, 255, 0.05);
319
+ border-radius: 3px;
320
+ }
321
+
322
+ .messages-container::-webkit-scrollbar-thumb {
323
+ background: rgba(255, 255, 255, 0.2);
324
+ border-radius: 3px;
325
+ }
326
+
327
+ /* House Dialog */
328
+ .house-dialog {
329
+ display: none;
330
+ position: fixed;
331
+ top: 0;
332
+ left: 0;
333
+ right: 0;
334
+ bottom: 0;
335
+ background: rgba(0, 0, 0, 0.7);
336
+ z-index: 300;
337
+ align-items: center;
338
+ justify-content: center;
339
+ }
340
+
341
+ .house-dialog.active {
342
+ display: flex;
343
+ }
344
+
345
+ .house-dialog-content {
346
+ background: rgba(255, 255, 255, 0.15);
347
+ backdrop-filter: blur(20px);
348
+ border-radius: 20px;
349
+ border: 1px solid rgba(255, 255, 255, 0.2);
350
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
351
+ width: 500px;
352
+ max-width: 90%;
353
+ max-height: 80vh;
354
+ display: flex;
355
+ flex-direction: column;
356
+ }
357
+
358
+ .house-dialog-header {
359
+ padding: 20px;
360
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
361
+ display: flex;
362
+ justify-content: space-between;
363
+ align-items: center;
364
+ }
365
+
366
+ .house-dialog-header h2 {
367
+ color: white;
368
+ font-size: 1.2rem;
369
+ font-weight: 600;
370
+ }
371
+
372
+ .house-dialog-body {
373
+ padding: 20px;
374
+ overflow-y: auto;
375
+ max-height: 60vh;
376
+ }
377
+
378
+ .house-dialog-body .message-card {
379
+ background: rgba(255, 255, 255, 0.1);
380
+ border-radius: 12px;
381
+ padding: 12px;
382
+ margin-bottom: 10px;
383
+ border-left: 3px solid #4CAF50;
384
+ }
385
+
386
+ .house-dialog-body .message-card.unread {
387
+ border-left-color: #ff6b6b;
388
+ background: rgba(255, 107, 107, 0.15);
389
+ }
390
+
391
+ /* Tabs */
392
+ .house-dialog-tabs {
393
+ display: flex;
394
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
395
+ }
396
+
397
+ .tab-btn {
398
+ flex: 1;
399
+ padding: 12px;
400
+ background: transparent;
401
+ border: none;
402
+ color: rgba(255, 255, 255, 0.7);
403
+ font-size: 0.9rem;
404
+ font-weight: 600;
405
+ cursor: pointer;
406
+ transition: all 0.3s ease;
407
+ position: relative;
408
+ }
409
+
410
+ .tab-btn:hover {
411
+ color: white;
412
+ background: rgba(255, 255, 255, 0.1);
413
+ }
414
+
415
+ .tab-btn.active {
416
+ color: white;
417
+ background: rgba(255, 255, 255, 0.15);
418
+ }
419
+
420
+ .tab-btn.active::after {
421
+ content: '';
422
+ position: absolute;
423
+ bottom: 0;
424
+ left: 20%;
425
+ right: 20%;
426
+ height: 3px;
427
+ background: #667eea;
428
+ border-radius: 2px;
429
+ }
430
+
431
+ .tab-badge {
432
+ background: #ff6b6b;
433
+ color: white;
434
+ padding: 2px 8px;
435
+ border-radius: 12px;
436
+ font-size: 0.75rem;
437
+ margin-left: 6px;
438
+ }
439
+
440
+ .tab-badge:empty,
441
+ .tab-badge[style*="display: none"] {
442
+ display: none;
443
+ }
444
+ </style>
445
+ </head>
446
+ <body>
447
+ <div id="canvas-container"></div>
448
+
449
+ <!-- Collapsible Send Panel -->
450
+ <div class="send-panel" id="send-panel">
451
+ <div class="send-header" onclick="toggleSendPanel()">
452
+ <h2>💬 Send Message</h2>
453
+ <button class="collapse-btn" id="collapse-btn">−</button>
454
+ </div>
455
+ <div class="send-form">
456
+ <select id="recipient-select" class="recipient-select">
457
+ <option value="">To: Select agent...</option>
458
+ </select>
459
+ <textarea id="message-input" class="message-input" placeholder="Type your message..."></textarea>
460
+ <button id="send-btn" class="send-btn">Send</button>
461
+ </div>
462
+ </div>
463
+
464
+ <!-- Toggle Messages Button -->
465
+ <button class="toggle-messages-btn" id="toggle-messages-btn" onclick="toggleMessagesPanel()">
466
+ 📨 Messages <span class="badge" id="unread-badge" style="display: none;">0</span>
467
+ </button>
468
+
469
+ <!-- Messages Panel (Slide from Right) -->
470
+ <div class="messages-panel" id="messages-panel">
471
+ <div class="messages-header">
472
+ <h2>📨 Message History</h2>
473
+ <button class="close-btn" onclick="toggleMessagesPanel()">×</button>
474
+ </div>
475
+ <div class="messages-container" id="messages-container">
476
+ <div class="empty-state">
477
+ <div style="font-size: 2rem; margin-bottom: 8px;">📭</div>
478
+ <p>No messages yet</p>
479
+ </div>
480
+ </div>
481
+ </div>
482
+
483
+ <!-- House Dialog - Shows messages with specific agent -->
484
+ <div class="house-dialog" id="house-dialog">
485
+ <div class="house-dialog-content">
486
+ <div class="house-dialog-header">
487
+ <h2 id="house-dialog-title">Messages</h2>
488
+ <button class="close-btn" onclick="closeHouseDialog()">×</button>
489
+ </div>
490
+ <div class="house-dialog-tabs">
491
+ <button class="tab-btn active" id="tab-received" onclick="switchTab('received')">
492
+ 📥 Received <span id="received-count" class="tab-badge"></span>
493
+ </button>
494
+ <button class="tab-btn" id="tab-sent" onclick="switchTab('sent')">
495
+ 📤 Sent <span id="sent-count" class="tab-badge"></span>
496
+ </button>
497
+ </div>
498
+ <div class="house-dialog-body" id="house-dialog-content">
499
+ <!-- Messages will be loaded here -->
500
+ </div>
501
+ </div>
502
+ </div>
503
+
504
+ <div class="toast" id="toast">Message sent!</div>
505
+
506
+ <script type="importmap">
507
+ {
508
+ "imports": {
509
+ "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
510
+ "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
511
+ }
512
+ }
513
+ </script>
514
+ <script type="module" src="/app.js"></script>
515
+ </body>
516
+ </html>
package/server.ts ADDED
@@ -0,0 +1,192 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import Database from 'better-sqlite3';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const app = express();
10
+ const PORT = process.env.PORT || 3000;
11
+
12
+ // Parse CLI args
13
+ const args = process.argv.slice(2);
14
+ let user: string | null = null;
15
+ let mailboxPath: string | null = null;
16
+ let coworkerPath: string | null = null;
17
+
18
+ for (let i = 0; i < args.length; i++) {
19
+ if (args[i] === '--user' || args[i] === '-u') {
20
+ user = args[++i];
21
+ } else if (args[i] === '--mailbox' || args[i] === '-m') {
22
+ mailboxPath = args[++i];
23
+ } else if (args[i] === '--coworkers' || args[i] === '-c') {
24
+ coworkerPath = args[++i];
25
+ }
26
+ }
27
+
28
+ if (!user || !mailboxPath) {
29
+ console.error('Usage: watercooler --user <name> --mailbox <path> [--coworkers <path>]');
30
+ process.exit(1);
31
+ }
32
+
33
+ console.log(`🚰 Watercooler for ${user}`);
34
+ console.log(` Mailbox: ${mailboxPath}`);
35
+ if (coworkerPath) {
36
+ console.log(` Coworker DB: ${coworkerPath}`);
37
+ }
38
+ console.log(` URL: http://localhost:${PORT}`);
39
+
40
+ // Databases
41
+ let db: Database.Database | null = null;
42
+ let coworkerDb: Database.Database | null = null;
43
+
44
+ try {
45
+ db = new Database(mailboxPath);
46
+ console.log(' Mailbox DB: connected');
47
+ } catch (err: any) {
48
+ console.error(' Mailbox DB error:', err.message);
49
+ process.exit(1);
50
+ }
51
+
52
+ if (coworkerPath) {
53
+ try {
54
+ coworkerDb = new Database(coworkerPath);
55
+ console.log(' Coworker DB: connected');
56
+ } catch (err: any) {
57
+ console.warn(' Coworker DB error:', err.message);
58
+ }
59
+ }
60
+
61
+ // Middleware
62
+ app.use(express.json());
63
+ app.use(express.static(path.join(__dirname, 'public')));
64
+
65
+ // API: Get inbox (messages TO user)
66
+ app.get('/api/messages', (req, res) => {
67
+ try {
68
+ if (!db) throw new Error('Database not connected');
69
+ const stmt = db.prepare(`
70
+ SELECT * FROM messages
71
+ WHERE recipient = ?
72
+ ORDER BY timestamp DESC
73
+ `);
74
+ res.json(stmt.all(user!.toLowerCase()));
75
+ } catch (err: any) {
76
+ res.status(500).json({ error: err.message });
77
+ }
78
+ });
79
+
80
+ // API: Get sent messages (messages FROM user)
81
+ app.get('/api/messages/sent', (req, res) => {
82
+ try {
83
+ if (!db) throw new Error('Database not connected');
84
+ const stmt = db.prepare(`
85
+ SELECT * FROM messages
86
+ WHERE sender = ?
87
+ ORDER BY timestamp DESC
88
+ `);
89
+ res.json(stmt.all(user!.toLowerCase()));
90
+ } catch (err: any) {
91
+ res.status(500).json({ error: err.message });
92
+ }
93
+ });
94
+
95
+ // API: Get ALL messages involving the user (for house dialogs)
96
+ app.get('/api/messages/all', (req, res) => {
97
+ try {
98
+ if (!db) throw new Error('Database not connected');
99
+ const stmt = db.prepare(`
100
+ SELECT * FROM messages
101
+ WHERE recipient = ? OR sender = ?
102
+ ORDER BY timestamp DESC
103
+ `);
104
+ res.json(stmt.all(user!.toLowerCase(), user!.toLowerCase()));
105
+ } catch (err: any) {
106
+ res.status(500).json({ error: err.message });
107
+ }
108
+ });
109
+
110
+ // API: Get all coworkers (from coworker.db + message recipients)
111
+ app.get('/api/coworkers', (req, res) => {
112
+ try {
113
+ if (!db) throw new Error('Database not connected');
114
+ const allCoworkers = new Set<string>();
115
+
116
+ // Add from coworker.db if available
117
+ if (coworkerDb) {
118
+ try {
119
+ const rows = coworkerDb.prepare('SELECT name FROM coworkers').all() as Array<{name: string}>;
120
+
121
+ rows.forEach(row => allCoworkers.add(row.name.toLowerCase()));
122
+ } catch (err: any) {
123
+ console.error('Error reading coworker.db:', err.message);
124
+ }
125
+ } else {
126
+ console.log('No coworkerDb connection available');
127
+ }
128
+
129
+ // Add from message history
130
+ const recipientRows = db.prepare('SELECT DISTINCT recipient FROM messages').all() as Array<{recipient: string}>;
131
+ recipientRows.forEach(row => allCoworkers.add(row.recipient.toLowerCase()));
132
+
133
+ const senderRows = db.prepare('SELECT DISTINCT sender FROM messages').all() as Array<{sender: string}>;
134
+ senderRows.forEach(row => allCoworkers.add(row.sender.toLowerCase()));
135
+
136
+ // Remove current user
137
+ allCoworkers.delete(user!.toLowerCase());
138
+
139
+ const result = Array.from(allCoworkers).sort();
140
+ res.json(result);
141
+ } catch (err: any) {
142
+ console.error('Error in /api/coworkers:', err.message);
143
+ res.status(500).json({ error: err.message });
144
+ }
145
+ });
146
+
147
+ // Legacy: Get recipients (for backwards compat)
148
+ app.get('/api/recipients', (req, res) => {
149
+ try {
150
+ if (!db) throw new Error('Database not connected');
151
+ const stmt = db.prepare(`SELECT DISTINCT recipient FROM messages`);
152
+ res.json(stmt.all().map((r: any) => r.recipient));
153
+ } catch (err: any) {
154
+ res.status(500).json({ error: err.message });
155
+ }
156
+ });
157
+
158
+ // API: Send message
159
+ app.post('/api/send', (req, res) => {
160
+ try {
161
+ if (!db) throw new Error('Database not connected');
162
+ const { to, message } = req.body;
163
+ const stmt = db.prepare(`
164
+ INSERT INTO messages (recipient, sender, message, timestamp, read)
165
+ VALUES (?, ?, ?, ?, 0)
166
+ `);
167
+ stmt.run(to.toLowerCase(), user!.toLowerCase(), message, Date.now());
168
+ res.json({ success: true });
169
+ } catch (err: any) {
170
+ res.status(500).json({ error: err.message });
171
+ }
172
+ });
173
+
174
+ // API: Mark read
175
+ app.post('/api/messages/:id/read', (req, res) => {
176
+ try {
177
+ if (!db) throw new Error('Database not connected');
178
+ db.prepare('UPDATE messages SET read = 1 WHERE id = ?').run(req.params.id);
179
+ res.json({ success: true });
180
+ } catch (err: any) {
181
+ res.status(500).json({ error: err.message });
182
+ }
183
+ });
184
+
185
+ // Config endpoint
186
+ app.get('/api/config', (req, res) => {
187
+ res.json({ user, mailbox: mailboxPath, coworker: coworkerPath });
188
+ });
189
+
190
+ app.listen(PORT, () => {
191
+ console.log('\n✅ Watercooler running!');
192
+ });