watercooler 0.0.4 → 0.0.6

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.
package/public/index.html CHANGED
@@ -199,6 +199,23 @@
199
199
  opacity: 1;
200
200
  }
201
201
 
202
+ .mark-all-read-btn {
203
+ background: rgba(255, 255, 255, 0.1);
204
+ border: 1px solid rgba(255, 255, 255, 0.2);
205
+ color: rgba(255, 255, 255, 0.8);
206
+ font-size: 0.75rem;
207
+ padding: 4px 10px;
208
+ border-radius: 4px;
209
+ cursor: pointer;
210
+ transition: all 0.2s;
211
+ white-space: nowrap;
212
+ }
213
+
214
+ .mark-all-read-btn:hover {
215
+ background: rgba(255, 255, 255, 0.2);
216
+ color: white;
217
+ }
218
+
202
219
  .messages-container {
203
220
  flex: 1;
204
221
  overflow-y: auto;
@@ -303,7 +320,7 @@
303
320
  }
304
321
 
305
322
  .message-text li {
306
- margin: 4px 0;
323
+ margin: 2px 0;
307
324
  }
308
325
 
309
326
  /* Toggle Messages Button */
@@ -521,7 +538,7 @@
521
538
  transform: translateY(-50%) translateY(0);
522
539
  }
523
540
 
524
- /* House dialog - full screen on mobile */
541
+ /* Desk dialog - full screen on mobile */
525
542
  .house-dialog-content {
526
543
  width: 100%;
527
544
  max-width: 100%;
@@ -615,7 +632,7 @@
615
632
  }
616
633
  }
617
634
 
618
- /* House Dialog */
635
+ /* Desk Dialog */
619
636
  .house-dialog {
620
637
  display: none;
621
638
  position: fixed;
@@ -633,7 +650,7 @@
633
650
  display: flex;
634
651
  }
635
652
 
636
- /* Desktop override for house dialog */
653
+ /* Desktop override for desk dialog */
637
654
  @media (min-width: 769px) {
638
655
  .house-dialog {
639
656
  align-items: center;
@@ -648,7 +665,7 @@
648
665
  }
649
666
  }
650
667
 
651
- /* Desktop house dialog styles */
668
+ /* Desktop desk dialog styles */
652
669
  @media (min-width: 769px) {
653
670
  .house-dialog-content {
654
671
  background: rgba(255, 255, 255, 0.15);
@@ -664,7 +681,7 @@
664
681
  }
665
682
  }
666
683
 
667
- /* Base house dialog styles (shared) */
684
+ /* Base desk dialog styles (shared) */
668
685
  .house-dialog-content {
669
686
  background: rgba(255, 255, 255, 0.15);
670
687
  backdrop-filter: blur(20px);
@@ -790,7 +807,10 @@
790
807
  <div class="messages-panel" id="messages-panel">
791
808
  <div class="messages-header">
792
809
  <h2>📨 Message History</h2>
793
- <button class="close-btn" onclick="toggleMessagesPanel()">×</button>
810
+ <div style="display: flex; gap: 8px; align-items: center;">
811
+ <button class="mark-all-read-btn" onclick="markAllAsRead()">Mark all as read</button>
812
+ <button class="close-btn" onclick="toggleMessagesPanel()">×</button>
813
+ </div>
794
814
  </div>
795
815
  <div class="messages-container" id="messages-container">
796
816
  <div class="empty-state">
@@ -800,12 +820,12 @@
800
820
  </div>
801
821
  </div>
802
822
 
803
- <!-- House Dialog - Shows messages with specific agent -->
823
+ <!-- Desk Dialog - Shows messages with specific agent -->
804
824
  <div class="house-dialog" id="house-dialog">
805
825
  <div class="house-dialog-content">
806
826
  <div class="house-dialog-header">
807
827
  <h2 id="house-dialog-title">Messages</h2>
808
- <button class="close-btn" onclick="closeHouseDialog()">×</button>
828
+ <button class="close-btn" onclick="closeDeskDialog()">×</button>
809
829
  </div>
810
830
  <div class="house-dialog-tabs">
811
831
  <button class="tab-btn active" id="tab-received" onclick="switchTab('received')">
package/server.ts CHANGED
@@ -13,6 +13,7 @@ const args = process.argv.slice(2);
13
13
  let user: string | null = null;
14
14
  let mailboxPath: string | null = null;
15
15
  let coworkerPath: string | null = null;
16
+ let avatarPath: string | null = null;
16
17
  let port: number = parseInt(process.env.PORT || '3000', 10);
17
18
  let host: string = process.env.HOST || '0.0.0.0';
18
19
 
@@ -23,6 +24,8 @@ for (let i = 0; i < args.length; i++) {
23
24
  mailboxPath = args[++i];
24
25
  } else if (args[i] === '--coworkers' || args[i] === '-c') {
25
26
  coworkerPath = args[++i];
27
+ } else if (args[i] === '--avatars' || args[i] === '-a') {
28
+ avatarPath = args[++i];
26
29
  } else if (args[i] === '--port' || args[i] === '-p') {
27
30
  const p = parseInt(args[++i], 10);
28
31
  if (!isNaN(p)) port = p;
@@ -32,7 +35,7 @@ for (let i = 0; i < args.length; i++) {
32
35
  }
33
36
 
34
37
  if (!user || !mailboxPath) {
35
- console.error('Usage: watercooler --user <name> --mailbox <path> [--coworkers <path>] [--port <number>] [--host <address>]');
38
+ console.error('Usage: watercooler --user <name> --mailbox <path> [--coworkers <path>] [--avatars <path>] [--port <number>] [--host <address>]');
36
39
  process.exit(1);
37
40
  }
38
41
 
@@ -41,11 +44,15 @@ console.log(` Mailbox: ${mailboxPath}`);
41
44
  if (coworkerPath) {
42
45
  console.log(` Coworker DB: ${coworkerPath}`);
43
46
  }
47
+ if (avatarPath) {
48
+ console.log(` Avatar DB: ${avatarPath}`);
49
+ }
44
50
  console.log(` URL: http://${host}:${port}`);
45
51
 
46
52
  // Databases
47
53
  let db: Database.Database | null = null;
48
54
  let coworkerDb: Database.Database | null = null;
55
+ let avatarDb: Database.Database | null = null;
49
56
 
50
57
  try {
51
58
  db = new Database(mailboxPath);
@@ -64,6 +71,15 @@ if (coworkerPath) {
64
71
  }
65
72
  }
66
73
 
74
+ if (avatarPath) {
75
+ try {
76
+ avatarDb = new Database(avatarPath);
77
+ console.log(' Avatar DB: connected');
78
+ } catch (err: any) {
79
+ console.warn(' Avatar DB error:', err.message);
80
+ }
81
+ }
82
+
67
83
  // Helper: Check if table exists
68
84
  function tableExists(database: Database.Database | null, tableName: string): boolean {
69
85
  if (!database) return false;
@@ -231,9 +247,55 @@ app.post('/api/messages/:id/read', (req, res) => {
231
247
  }
232
248
  });
233
249
 
250
+ // API: Get avatar states (latest tool usage per coworker)
251
+ app.get('/api/avatars', (req, res) => {
252
+ try {
253
+ if (!avatarDb) {
254
+ res.json({});
255
+ return;
256
+ }
257
+
258
+ // Check if latest_tool_usage table exists
259
+ const tableCheck = avatarDb.prepare(`
260
+ SELECT name FROM sqlite_master
261
+ WHERE type='table' AND name='latest_tool_usage'
262
+ `).get();
263
+
264
+ if (!tableCheck) {
265
+ res.json({});
266
+ return;
267
+ }
268
+
269
+ // Get latest tool usage per name
270
+ const stmt = avatarDb.prepare(`
271
+ SELECT name, tool_name, timestamp
272
+ FROM latest_tool_usage
273
+ ORDER BY timestamp DESC
274
+ `);
275
+
276
+ const rows = stmt.all() as Array<{name: string; tool_name: string; timestamp: number}>;
277
+
278
+ // Build map of name -> latest tool (first occurrence is latest due to ORDER BY)
279
+ const avatarStates: Record<string, {tool_name: string; timestamp: number}> = {};
280
+ for (const row of rows) {
281
+ if (!avatarStates[row.name]) {
282
+ avatarStates[row.name] = {
283
+ tool_name: row.tool_name,
284
+ timestamp: row.timestamp
285
+ };
286
+ }
287
+ }
288
+
289
+ res.json(avatarStates);
290
+ } catch (err: any) {
291
+ console.error('Error in /api/avatars:', err.message);
292
+ res.status(500).json({ error: err.message });
293
+ }
294
+ });
295
+
234
296
  // Config endpoint
235
297
  app.get('/api/config', (req, res) => {
236
- res.json({ user, mailbox: mailboxPath, coworker: coworkerPath });
298
+ res.json({ user, mailbox: mailboxPath, coworker: coworkerPath, avatar: avatarPath });
237
299
  });
238
300
 
239
301
  app.listen(port, host, () => {