watercooler 0.0.9 → 0.0.10

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/README.md CHANGED
@@ -1,6 +1,9 @@
1
- # Watercooler Village
2
1
 
3
- A beautiful 3D visualization of your mailbox messages as a village of coworkers.
2
+ <div align="center"><img width="500" alt="image" src="https://github.com/user-attachments/assets/a4ab0748-cd1b-43d6-9d8b-35e64eb954a2" /></div>
3
+
4
+ # WaterCooler
5
+
6
+ A beautiful 3D visualization of your office of autonomous AI coworkers and their communication.
4
7
 
5
8
  ## Installation
6
9
 
@@ -45,7 +48,7 @@ npm start -- --user richard --mailbox ~/.config/opencode/mailbox.db
45
48
 
46
49
  ## Features
47
50
 
48
- - **3D Village**: Each coworker appears as a colorful house in a circle
51
+ - **3D Office**: Each coworker appears as a colorful house in a circle
49
52
  - **Message Flow**: Animated particles show messages traveling between houses
50
53
  - **Visual Status**:
51
54
  - Green lines = read messages
@@ -59,7 +62,7 @@ npm start -- --user richard --mailbox ~/.config/opencode/mailbox.db
59
62
 
60
63
  - **Backend**: Express server with SQLite
61
64
  - **Frontend**: Vanilla JavaScript + Three.js (from CDN)
62
- - **TypeScript**: Runs directly with tsx (included as a dependency)
65
+ - **TypeScript**: Compiled to JavaScript for production
63
66
 
64
67
  ## Database Integration
65
68
 
@@ -69,4 +72,4 @@ Contains messages table with: id, recipient, sender, message, timestamp, read
69
72
  ### Coworker DB (optional)
70
73
  Contains coworkers table with: name, session_id, agent_type, created_at, parent_id
71
74
 
72
- When provided, watercooler shows ALL coworkers from the database, regardless of whether they have sent/received messages yet.
75
+ When provided, watercooler shows ALL coworkers from the database, regardless of whether they have sent/received messages yet.
@@ -6,9 +6,9 @@ import { dirname, join } from 'path';
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
8
8
 
9
- const serverPath = join(__dirname, '..', 'server.ts');
9
+ const serverPath = join(__dirname, '..', 'dist', 'server.js');
10
10
 
11
- const child = spawn('tsx', [serverPath, ...process.argv.slice(2)], {
11
+ const child = spawn('node', [serverPath, ...process.argv.slice(2)], {
12
12
  stdio: 'inherit',
13
13
  shell: false
14
14
  });
package/dist/server.js ADDED
@@ -0,0 +1,296 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import Database from 'better-sqlite3';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const app = express();
8
+ // Parse CLI args
9
+ const args = process.argv.slice(2);
10
+ let user = null;
11
+ let mailboxPath = null;
12
+ let coworkerPath = null;
13
+ let statusPath = null;
14
+ let port = parseInt(process.env.PORT || '3000', 10);
15
+ let host = process.env.HOST || '0.0.0.0';
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === '--user' || args[i] === '-u') {
18
+ user = args[++i];
19
+ }
20
+ else if (args[i] === '--mailbox' || args[i] === '-m') {
21
+ mailboxPath = args[++i];
22
+ }
23
+ else if (args[i] === '--coworkers' || args[i] === '-c') {
24
+ coworkerPath = args[++i];
25
+ }
26
+ else if (args[i] === '--status' || args[i] === '-s') {
27
+ statusPath = args[++i];
28
+ }
29
+ else if (args[i] === '--port' || args[i] === '-p') {
30
+ const p = parseInt(args[++i], 10);
31
+ if (!isNaN(p))
32
+ port = p;
33
+ }
34
+ else if (args[i] === '--host' || args[i] === '-h') {
35
+ host = args[++i];
36
+ }
37
+ }
38
+ if (!user || !mailboxPath) {
39
+ console.error('Usage: watercooler --user <name> --mailbox <path> [--coworkers <path>] [--status <path>] [--port <number>] [--host <address>]');
40
+ process.exit(1);
41
+ }
42
+ console.log(`🚰 Watercooler for ${user}`);
43
+ console.log(` Mailbox: ${mailboxPath}`);
44
+ if (coworkerPath) {
45
+ console.log(` Coworker DB: ${coworkerPath}`);
46
+ }
47
+ if (statusPath) {
48
+ console.log(` Status DB: ${statusPath}`);
49
+ }
50
+ console.log(` URL: http://${host}:${port}`);
51
+ // Databases
52
+ let db = null;
53
+ let coworkerDb = null;
54
+ let statusDb = null;
55
+ try {
56
+ db = new Database(mailboxPath);
57
+ console.log(' Mailbox DB: connected');
58
+ }
59
+ catch (err) {
60
+ console.error(' Mailbox DB error:', err.message);
61
+ process.exit(1);
62
+ }
63
+ if (coworkerPath) {
64
+ try {
65
+ coworkerDb = new Database(coworkerPath);
66
+ console.log(' Coworker DB: connected');
67
+ }
68
+ catch (err) {
69
+ console.warn(' Coworker DB error:', err.message);
70
+ }
71
+ }
72
+ if (statusPath) {
73
+ try {
74
+ statusDb = new Database(statusPath);
75
+ console.log(' Status DB: connected');
76
+ }
77
+ catch (err) {
78
+ console.warn(' Status DB error:', err.message);
79
+ }
80
+ }
81
+ // Helper: Check if table exists
82
+ function tableExists(database, tableName) {
83
+ if (!database)
84
+ return false;
85
+ try {
86
+ const stmt = database.prepare(`
87
+ SELECT name FROM sqlite_master
88
+ WHERE type='table' AND name=?
89
+ `);
90
+ return !!stmt.get(tableName);
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ }
96
+ // Middleware
97
+ app.use(express.json());
98
+ app.use(express.static(path.join(__dirname, 'public')));
99
+ // API: Get inbox (messages TO user)
100
+ app.get('/api/messages', (req, res) => {
101
+ try {
102
+ if (!db)
103
+ throw new Error('Database not connected');
104
+ if (!tableExists(db, 'messages')) {
105
+ res.json([]);
106
+ return;
107
+ }
108
+ const stmt = db.prepare(`
109
+ SELECT * FROM messages
110
+ WHERE recipient = ?
111
+ ORDER BY timestamp DESC
112
+ `);
113
+ res.json(stmt.all(user.toLowerCase()));
114
+ }
115
+ catch (err) {
116
+ res.status(500).json({ error: err.message });
117
+ }
118
+ });
119
+ // API: Get sent messages (messages FROM user)
120
+ app.get('/api/messages/sent', (req, res) => {
121
+ try {
122
+ if (!db)
123
+ throw new Error('Database not connected');
124
+ if (!tableExists(db, 'messages')) {
125
+ res.json([]);
126
+ return;
127
+ }
128
+ const stmt = db.prepare(`
129
+ SELECT * FROM messages
130
+ WHERE sender = ?
131
+ ORDER BY timestamp DESC
132
+ `);
133
+ res.json(stmt.all(user.toLowerCase()));
134
+ }
135
+ catch (err) {
136
+ res.status(500).json({ error: err.message });
137
+ }
138
+ });
139
+ // API: Get ALL messages between ALL agents
140
+ app.get('/api/messages/all', (req, res) => {
141
+ try {
142
+ if (!db)
143
+ throw new Error('Database not connected');
144
+ if (!tableExists(db, 'messages')) {
145
+ res.json([]);
146
+ return;
147
+ }
148
+ const stmt = db.prepare(`
149
+ SELECT * FROM messages
150
+ ORDER BY timestamp DESC
151
+ `);
152
+ res.json(stmt.all());
153
+ }
154
+ catch (err) {
155
+ res.status(500).json({ error: err.message });
156
+ }
157
+ });
158
+ // API: Get all coworkers (from coworker.db + message recipients)
159
+ app.get('/api/coworkers', (req, res) => {
160
+ try {
161
+ if (!db)
162
+ throw new Error('Database not connected');
163
+ const allCoworkers = new Set();
164
+ // Add from coworker.db if available
165
+ if (coworkerDb) {
166
+ try {
167
+ const rows = coworkerDb.prepare('SELECT name FROM coworkers').all();
168
+ rows.forEach(row => allCoworkers.add(row.name.toLowerCase()));
169
+ }
170
+ catch (err) {
171
+ console.error('Error reading coworker.db:', err.message);
172
+ }
173
+ }
174
+ else {
175
+ console.log('No coworkerDb connection available');
176
+ }
177
+ // Note: Messages table is in a different database, not queried here
178
+ // Remove current user
179
+ allCoworkers.delete(user.toLowerCase());
180
+ const result = Array.from(allCoworkers).sort();
181
+ res.json(result);
182
+ }
183
+ catch (err) {
184
+ console.error('Error in /api/coworkers:', err.message);
185
+ res.status(500).json({ error: err.message });
186
+ }
187
+ });
188
+ // Legacy: Get recipients (for backwards compat)
189
+ app.get('/api/recipients', (req, res) => {
190
+ try {
191
+ if (!db)
192
+ throw new Error('Database not connected');
193
+ if (!tableExists(db, 'messages')) {
194
+ res.json([]);
195
+ return;
196
+ }
197
+ const stmt = db.prepare(`SELECT DISTINCT recipient FROM messages`);
198
+ res.json(stmt.all().map((r) => r.recipient));
199
+ }
200
+ catch (err) {
201
+ res.status(500).json({ error: err.message });
202
+ }
203
+ });
204
+ // API: Send message
205
+ app.post('/api/send', (req, res) => {
206
+ try {
207
+ if (!db)
208
+ throw new Error('Database not connected');
209
+ // Auto-create messages table if it doesn't exist
210
+ if (!tableExists(db, 'messages')) {
211
+ db.exec(`
212
+ CREATE TABLE messages (
213
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
214
+ recipient TEXT NOT NULL,
215
+ sender TEXT NOT NULL,
216
+ message TEXT NOT NULL,
217
+ timestamp INTEGER NOT NULL,
218
+ read INTEGER DEFAULT 0
219
+ )
220
+ `);
221
+ }
222
+ const { to, message } = req.body;
223
+ const stmt = db.prepare(`
224
+ INSERT INTO messages (recipient, sender, message, timestamp, read)
225
+ VALUES (?, ?, ?, ?, 0)
226
+ `);
227
+ stmt.run(to.toLowerCase(), user.toLowerCase(), message, Date.now());
228
+ res.json({ success: true });
229
+ }
230
+ catch (err) {
231
+ res.status(500).json({ error: err.message });
232
+ }
233
+ });
234
+ // API: Mark read
235
+ app.post('/api/messages/:id/read', (req, res) => {
236
+ try {
237
+ if (!db)
238
+ throw new Error('Database not connected');
239
+ if (!tableExists(db, 'messages')) {
240
+ res.status(404).json({ error: 'Messages table not found' });
241
+ return;
242
+ }
243
+ db.prepare('UPDATE messages SET read = 1 WHERE id = ?').run(req.params.id);
244
+ res.json({ success: true });
245
+ }
246
+ catch (err) {
247
+ res.status(500).json({ error: err.message });
248
+ }
249
+ });
250
+ // API: Get status states (latest tool usage per coworker)
251
+ app.get('/api/status', (req, res) => {
252
+ try {
253
+ if (!statusDb) {
254
+ res.json({});
255
+ return;
256
+ }
257
+ // Check if latest_tool_usage table exists
258
+ const tableCheck = statusDb.prepare(`
259
+ SELECT name FROM sqlite_master
260
+ WHERE type='table' AND name='latest_tool_usage'
261
+ `).get();
262
+ if (!tableCheck) {
263
+ res.json({});
264
+ return;
265
+ }
266
+ // Get latest tool usage per name
267
+ const stmt = statusDb.prepare(`
268
+ SELECT name, tool_name, timestamp
269
+ FROM latest_tool_usage
270
+ ORDER BY timestamp DESC
271
+ `);
272
+ const rows = stmt.all();
273
+ // Build map of name -> latest tool (first occurrence is latest due to ORDER BY)
274
+ const statusStates = {};
275
+ for (const row of rows) {
276
+ if (!statusStates[row.name]) {
277
+ statusStates[row.name] = {
278
+ tool_name: row.tool_name,
279
+ timestamp: row.timestamp
280
+ };
281
+ }
282
+ }
283
+ res.json(statusStates);
284
+ }
285
+ catch (err) {
286
+ console.error('Error in /api/status:', err.message);
287
+ res.status(500).json({ error: err.message });
288
+ }
289
+ });
290
+ // Config endpoint
291
+ app.get('/api/config', (req, res) => {
292
+ res.json({ user, mailbox: mailboxPath, coworker: coworkerPath, status: statusPath });
293
+ });
294
+ app.listen(port, host, () => {
295
+ console.log('\nāœ… Watercooler running!');
296
+ });
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
1
  {
2
2
  "name": "watercooler",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "A beautiful 3D visualization of your mailbox messages as a village of coworkers",
5
5
  "type": "module",
6
- "main": "server.ts",
6
+ "main": "dist/server.js",
7
7
  "bin": {
8
- "watercooler": "./bin/watercooler.js"
8
+ "watercooler": "bin/watercooler.js"
9
9
  },
10
10
  "files": [
11
11
  "bin/",
12
12
  "public/",
13
- "server.ts",
13
+ "dist/",
14
14
  "README.md",
15
15
  "LICENSE"
16
16
  ],
17
17
  "scripts": {
18
- "start": "tsx --watch server.ts"
18
+ "start": "node ./dist/server.js",
19
+ "build": "tsc"
19
20
  },
20
21
  "keywords": [
21
22
  "watercooler",
@@ -36,8 +37,7 @@
36
37
  },
37
38
  "dependencies": {
38
39
  "better-sqlite3": "^11.8.1",
39
- "express": "^4.21.2",
40
- "tsx": "^4.19.2"
40
+ "express": "^4.21.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/better-sqlite3": "^7.6.12",
package/server.ts DELETED
@@ -1,303 +0,0 @@
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
-
11
- // Parse CLI args
12
- const args = process.argv.slice(2);
13
- let user: string | null = null;
14
- let mailboxPath: string | null = null;
15
- let coworkerPath: string | null = null;
16
- let statusPath: string | null = null;
17
- let port: number = parseInt(process.env.PORT || '3000', 10);
18
- let host: string = process.env.HOST || '0.0.0.0';
19
-
20
- for (let i = 0; i < args.length; i++) {
21
- if (args[i] === '--user' || args[i] === '-u') {
22
- user = args[++i];
23
- } else if (args[i] === '--mailbox' || args[i] === '-m') {
24
- mailboxPath = args[++i];
25
- } else if (args[i] === '--coworkers' || args[i] === '-c') {
26
- coworkerPath = args[++i];
27
- } else if (args[i] === '--status' || args[i] === '-s') {
28
- statusPath = args[++i];
29
- } else if (args[i] === '--port' || args[i] === '-p') {
30
- const p = parseInt(args[++i], 10);
31
- if (!isNaN(p)) port = p;
32
- } else if (args[i] === '--host' || args[i] === '-h') {
33
- host = args[++i];
34
- }
35
- }
36
-
37
- if (!user || !mailboxPath) {
38
- console.error('Usage: watercooler --user <name> --mailbox <path> [--coworkers <path>] [--status <path>] [--port <number>] [--host <address>]');
39
- process.exit(1);
40
- }
41
-
42
- console.log(`🚰 Watercooler for ${user}`);
43
- console.log(` Mailbox: ${mailboxPath}`);
44
- if (coworkerPath) {
45
- console.log(` Coworker DB: ${coworkerPath}`);
46
- }
47
- if (statusPath) {
48
- console.log(` Status DB: ${statusPath}`);
49
- }
50
- console.log(` URL: http://${host}:${port}`);
51
-
52
- // Databases
53
- let db: Database.Database | null = null;
54
- let coworkerDb: Database.Database | null = null;
55
- let statusDb: Database.Database | null = null;
56
-
57
- try {
58
- db = new Database(mailboxPath);
59
- console.log(' Mailbox DB: connected');
60
- } catch (err: any) {
61
- console.error(' Mailbox DB error:', err.message);
62
- process.exit(1);
63
- }
64
-
65
- if (coworkerPath) {
66
- try {
67
- coworkerDb = new Database(coworkerPath);
68
- console.log(' Coworker DB: connected');
69
- } catch (err: any) {
70
- console.warn(' Coworker DB error:', err.message);
71
- }
72
- }
73
-
74
- if (statusPath) {
75
- try {
76
- statusDb = new Database(statusPath);
77
- console.log(' Status DB: connected');
78
- } catch (err: any) {
79
- console.warn(' Status DB error:', err.message);
80
- }
81
- }
82
-
83
- // Helper: Check if table exists
84
- function tableExists(database: Database.Database | null, tableName: string): boolean {
85
- if (!database) return false;
86
- try {
87
- const stmt = database.prepare(`
88
- SELECT name FROM sqlite_master
89
- WHERE type='table' AND name=?
90
- `);
91
- return !!stmt.get(tableName);
92
- } catch {
93
- return false;
94
- }
95
- }
96
-
97
- // Middleware
98
- app.use(express.json());
99
- app.use(express.static(path.join(__dirname, 'public')));
100
-
101
- // API: Get inbox (messages TO user)
102
- app.get('/api/messages', (req, res) => {
103
- try {
104
- if (!db) throw new Error('Database not connected');
105
- if (!tableExists(db, 'messages')) {
106
- res.json([]);
107
- return;
108
- }
109
- const stmt = db.prepare(`
110
- SELECT * FROM messages
111
- WHERE recipient = ?
112
- ORDER BY timestamp DESC
113
- `);
114
- res.json(stmt.all(user!.toLowerCase()));
115
- } catch (err: any) {
116
- res.status(500).json({ error: err.message });
117
- }
118
- });
119
-
120
- // API: Get sent messages (messages FROM user)
121
- app.get('/api/messages/sent', (req, res) => {
122
- try {
123
- if (!db) throw new Error('Database not connected');
124
- if (!tableExists(db, 'messages')) {
125
- res.json([]);
126
- return;
127
- }
128
- const stmt = db.prepare(`
129
- SELECT * FROM messages
130
- WHERE sender = ?
131
- ORDER BY timestamp DESC
132
- `);
133
- res.json(stmt.all(user!.toLowerCase()));
134
- } catch (err: any) {
135
- res.status(500).json({ error: err.message });
136
- }
137
- });
138
-
139
- // API: Get ALL messages between ALL agents
140
- app.get('/api/messages/all', (req, res) => {
141
- try {
142
- if (!db) throw new Error('Database not connected');
143
- if (!tableExists(db, 'messages')) {
144
- res.json([]);
145
- return;
146
- }
147
- const stmt = db.prepare(`
148
- SELECT * FROM messages
149
- ORDER BY timestamp DESC
150
- `);
151
- res.json(stmt.all());
152
- } catch (err: any) {
153
- res.status(500).json({ error: err.message });
154
- }
155
- });
156
-
157
- // API: Get all coworkers (from coworker.db + message recipients)
158
- app.get('/api/coworkers', (req, res) => {
159
- try {
160
- if (!db) throw new Error('Database not connected');
161
- const allCoworkers = new Set<string>();
162
-
163
- // Add from coworker.db if available
164
- if (coworkerDb) {
165
- try {
166
- const rows = coworkerDb.prepare('SELECT name FROM coworkers').all() as Array<{name: string}>;
167
-
168
- rows.forEach(row => allCoworkers.add(row.name.toLowerCase()));
169
- } catch (err: any) {
170
- console.error('Error reading coworker.db:', err.message);
171
- }
172
- } else {
173
- console.log('No coworkerDb connection available');
174
- }
175
-
176
- // Note: Messages table is in a different database, not queried here
177
-
178
- // Remove current user
179
- allCoworkers.delete(user!.toLowerCase());
180
-
181
- const result = Array.from(allCoworkers).sort();
182
- res.json(result);
183
- } catch (err: any) {
184
- console.error('Error in /api/coworkers:', err.message);
185
- res.status(500).json({ error: err.message });
186
- }
187
- });
188
-
189
- // Legacy: Get recipients (for backwards compat)
190
- app.get('/api/recipients', (req, res) => {
191
- try {
192
- if (!db) throw new Error('Database not connected');
193
- if (!tableExists(db, 'messages')) {
194
- res.json([]);
195
- return;
196
- }
197
- const stmt = db.prepare(`SELECT DISTINCT recipient FROM messages`);
198
- res.json(stmt.all().map((r: any) => r.recipient));
199
- } catch (err: any) {
200
- res.status(500).json({ error: err.message });
201
- }
202
- });
203
-
204
- // API: Send message
205
- app.post('/api/send', (req, res) => {
206
- try {
207
- if (!db) throw new Error('Database not connected');
208
-
209
- // Auto-create messages table if it doesn't exist
210
- if (!tableExists(db, 'messages')) {
211
- db.exec(`
212
- CREATE TABLE messages (
213
- id INTEGER PRIMARY KEY AUTOINCREMENT,
214
- recipient TEXT NOT NULL,
215
- sender TEXT NOT NULL,
216
- message TEXT NOT NULL,
217
- timestamp INTEGER NOT NULL,
218
- read INTEGER DEFAULT 0
219
- )
220
- `);
221
- }
222
-
223
- const { to, message } = req.body;
224
- const stmt = db.prepare(`
225
- INSERT INTO messages (recipient, sender, message, timestamp, read)
226
- VALUES (?, ?, ?, ?, 0)
227
- `);
228
- stmt.run(to.toLowerCase(), user!.toLowerCase(), message, Date.now());
229
- res.json({ success: true });
230
- } catch (err: any) {
231
- res.status(500).json({ error: err.message });
232
- }
233
- });
234
-
235
- // API: Mark read
236
- app.post('/api/messages/:id/read', (req, res) => {
237
- try {
238
- if (!db) throw new Error('Database not connected');
239
- if (!tableExists(db, 'messages')) {
240
- res.status(404).json({ error: 'Messages table not found' });
241
- return;
242
- }
243
- db.prepare('UPDATE messages SET read = 1 WHERE id = ?').run(req.params.id);
244
- res.json({ success: true });
245
- } catch (err: any) {
246
- res.status(500).json({ error: err.message });
247
- }
248
- });
249
-
250
- // API: Get status states (latest tool usage per coworker)
251
- app.get('/api/status', (req, res) => {
252
- try {
253
- if (!statusDb) {
254
- res.json({});
255
- return;
256
- }
257
-
258
- // Check if latest_tool_usage table exists
259
- const tableCheck = statusDb.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 = statusDb.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 statusStates: Record<string, {tool_name: string; timestamp: number}> = {};
280
- for (const row of rows) {
281
- if (!statusStates[row.name]) {
282
- statusStates[row.name] = {
283
- tool_name: row.tool_name,
284
- timestamp: row.timestamp
285
- };
286
- }
287
- }
288
-
289
- res.json(statusStates);
290
- } catch (err: any) {
291
- console.error('Error in /api/status:', err.message);
292
- res.status(500).json({ error: err.message });
293
- }
294
- });
295
-
296
- // Config endpoint
297
- app.get('/api/config', (req, res) => {
298
- res.json({ user, mailbox: mailboxPath, coworker: coworkerPath, status: statusPath });
299
- });
300
-
301
- app.listen(port, host, () => {
302
- console.log('\nāœ… Watercooler running!');
303
- });