thinkncollab-shell 0.0.3 → 0.0.5

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,26 +1,402 @@
1
- # ThinkNCollab Shell
1
+ # ThinkNCollab Shell (`thinknsh`)
2
2
 
3
- A powerful collaborative shell with WebSocket integration for real-time team collaboration.
3
+ A powerful collaborative shell with WebSocket integration for real-time team collaboration — built on top of the ThinkNCollab platform.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Installation](#installation)
11
+ - [Quick Start](#quick-start)
12
+ - [Configuration](#configuration)
13
+ - [CLI Flags](#cli-flags)
14
+ - [Interactive Shell](#interactive-shell)
15
+ - [Commands Reference](#commands-reference)
16
+ - [Auth Commands](#auth-commands)
17
+ - [Team Commands](#team-commands)
18
+ - [Room Commands](#room-commands)
19
+ - [Message Commands](#message-commands)
20
+ - [Shell Commands](#shell-commands)
21
+ - [Single-Command Mode](#single-command-mode)
22
+ - [Built-in Shell Features](#built-in-shell-features)
23
+ - [Examples](#examples)
24
+ - [Environment Variables](#environment-variables)
25
+
26
+ ---
4
27
 
5
28
  ## Features
6
29
 
7
- - 🚀 **Custom Shell** - Full-featured command-line interface
8
- - 🔌 **WebSocket Integration** - Real-time communication with ThinkNCollab servers
9
- - 👥 **Room Collaboration** - Join rooms and collaborate with team members
10
- - 💬 **Messaging** - Send and receive messages in real-time
11
- - 🔔 **Notifications** - Get notified of important events
12
- - 📁 **File System** - Built-in file system commands
13
- - ⚡ **Command System** - Extensible command registration
14
- - 🔧 **Aliases** - Create custom command aliases
15
- - 📊 **Variables** - Shell variables support
16
- - 📜 **History** - Command history with persistence
17
- - 🎨 **Colored Output** - Beautiful colored terminal output
30
+ - 🚀 **Custom Shell** — Full-featured interactive command-line interface
31
+ - 🔌 **WebSocket Integration** — Real-time communication with ThinkNCollab servers
32
+ - 👥 **Room Collaboration** — Join rooms and collaborate with teammates live
33
+ - 💬 **Messaging** — Send and receive messages in real-time
34
+ - 🔔 **Notifications** — Get notified of tasks, uploads, and room events
35
+ - 📁 **File System** — Built-in file system commands (ls, cd, pwd, cat, mkdir)
36
+ - ⚡ **Command System** — Extensible command registration by category
37
+ - 🔧 **Aliases** — Create and manage custom command shortcuts
38
+ - 📊 **Variables** — Shell variable support (`set`, `$VAR`)
39
+ - 📜 **History** — Persistent command history across sessions
40
+ - 🎨 **Colored Output** — Beautiful chalk-powered terminal output
41
+
42
+ ---
18
43
 
19
44
  ## Installation
20
45
 
21
46
  ```bash
22
- # Install globally
47
+ # Install globally (recommended)
23
48
  npm install -g thinkncollab-shell
24
49
 
25
- # Or install locally
26
- npm install thinkncollab-shell
50
+ # Or install locally in a project
51
+ npm install thinkncollab-shell
52
+ ```
53
+
54
+ After global install, the `thinknsh` binary is available everywhere on your system.
55
+
56
+ ---
57
+
58
+ ## Quick Start
59
+
60
+ ```bash
61
+ # 1. Start the interactive shell
62
+ thinknsh
63
+
64
+ # 2. Log in to your ThinkNCollab account
65
+ thinknsh> login
66
+
67
+ # 3. Join a room
68
+ thinknsh> join my-room
69
+
70
+ # 4. Send a message
71
+ thinknsh> say Hello team!
72
+
73
+ # 5. Check your status
74
+ thinknsh> status
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Configuration
80
+
81
+ By default `thinknsh` connects to `https://thinkncollab.com`. In production, set environment variables before starting the shell:
82
+
83
+ ```bash
84
+ export THINKNCOLLAB_SERVER=https://thinkncollab.com
85
+ export THINKNCOLLAB_API_URL=https://thinkncollab.com
86
+ export THINKNCOLLAB_WS_URL=https://thinkncollab.com
87
+ thinknsh
88
+ ```
89
+
90
+ Or inline for a single session:
91
+
92
+ ```bash
93
+ THINKNCOLLAB_SERVER=https://thinkncollab.com thinknsh
94
+ ```
95
+
96
+ ---
97
+
98
+ ## CLI Flags
99
+
100
+ These flags are used **before** entering the interactive shell (passed directly to the `thinknsh` binary).
101
+
102
+ | Flag | Alias | Description |
103
+ |------|-------|-------------|
104
+ | `--help` | `-h` | Show help message and exit |
105
+ | `--version` | `-v` | Print the installed version and exit |
106
+
107
+ ```bash
108
+ thinknsh --help
109
+ thinknsh --version
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Interactive Shell
115
+
116
+ Running `thinknsh` with no arguments starts the **interactive shell** — a persistent REPL where you stay logged in, maintain a WebSocket connection, and run commands one after another.
117
+
118
+ ```
119
+ $ thinknsh
120
+
121
+ ████████╗██╗ ██╗██╗███╗ ██╗██╗ ██╗███╗ ██╗
122
+ ╚══██╔╝██║ ██║██║████╗ ██║██║ ██╔╝████╗ ██║
123
+ ██║ ███████║██║██╔██╗ ██║█████╔╝ ██╔██╗ ██║
124
+ ██║ ██╔══██║██║██║╚██╗██║██╔═██╗ ██║╚██╗██║
125
+ ██║ ██║ ██║██║██║ ╚████║██║ ██╗██║ ╚████║
126
+ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═══╝
127
+
128
+ thinknsh> _
129
+ ```
130
+
131
+ Type `help` inside the shell to list all available commands at any time.
132
+
133
+ ---
134
+
135
+ ## Commands Reference
136
+
137
+ ### Auth Commands
138
+
139
+ #### `login`
140
+ Authenticate with your ThinkNCollab account. Prompts for email and password interactively.
141
+
142
+ ```bash
143
+ thinknsh> login
144
+ # Enter your email: you@example.com
145
+ # Enter your password: ********
146
+ # ✅ Logged in as you@example.com
147
+ ```
148
+
149
+ #### `logout`
150
+ End your current session and clear saved credentials.
151
+
152
+ ```bash
153
+ thinknsh> logout
154
+ # ✅ Logged out successfully
155
+ ```
156
+
157
+ #### `whoami`
158
+ Display information about the currently authenticated user.
159
+
160
+ ```bash
161
+ thinknsh> whoami
162
+ # 👤 Logged in as: Ramanh (raman@thinkncollab.com)
163
+ # 🏢 Team: ThinkNCollab Core
164
+ ```
165
+
166
+ ---
167
+
168
+ ### Team Commands
169
+
170
+ #### `myteam`
171
+ Show details about your current team — name, plan, member count.
172
+
173
+ ```bash
174
+ thinknsh> myteam
175
+ # 🏢 Team: ThinkNCollab Core
176
+ # 📋 Plan: Pro
177
+ # 👥 Members: 8
178
+ ```
179
+
180
+ #### `teammates`
181
+ List all members in your team with their online status.
182
+
183
+ ```bash
184
+ thinknsh> teammates
185
+ # 👥 Team Members:
186
+ # 🟢 Raman Singh (you)
187
+ # 🟢 Omkar Yadav
188
+ # 🔴 Radhika Chauhan (offline)
189
+ # 🟡 -------------- (away)
190
+ ```
191
+
192
+ #### `invite <email>`
193
+ Send a team invitation to an email address.
194
+
195
+ ```bash
196
+ thinknsh> invite newmember@example.com
197
+ # ✅ Invitation sent to newmember@example.com
198
+ ```
199
+
200
+ ---
201
+
202
+ ### Room Commands
203
+
204
+ #### `join <room-id>`
205
+ Join a collaboration room. Establishes a WebSocket connection and streams live activity.
206
+
207
+ ```bash
208
+ thinknsh> join room-abc123
209
+ # 🚪 Joined room: room-abc123
210
+ # 👥 Members online: 3
211
+ # 📜 Recent activity loaded
212
+ ```
213
+
214
+ You will automatically receive real-time notifications for messages, task updates, and file uploads while inside a room.
215
+
216
+ #### `leave`
217
+ Leave the currently active room and disconnect from its WebSocket stream.
218
+
219
+ ```bash
220
+ thinknsh> leave
221
+ # 👋 Left room: room-abc123
222
+ ```
223
+
224
+ #### `rooms`
225
+ List all rooms available to you — showing name, ID, and member count.
226
+
227
+ ```bash
228
+ thinknsh> rooms
229
+ # 📁 Your Rooms:
230
+ # [1] frontend-team (ID: room-abc123) 👥 4 members
231
+ # [2] backend-infra (ID: room-def456) 👥 2 members
232
+ # [3] design-review (ID: room-ghi789) 👥 6 members
233
+ ```
234
+
235
+ ---
236
+
237
+ ### Message Commands
238
+
239
+ #### `say <message>`
240
+ Send a message to the room you are currently in. Requires you to be in a room (use `join` first).
241
+
242
+ ```bash
243
+ thinknsh> say Hey team, PR is ready for review!
244
+ # 💬 [You → frontend-team]: Hey team, PR is ready for review!
245
+ ```
246
+
247
+ Supports multi-word messages — no quotes needed.
248
+
249
+ ```bash
250
+ thinknsh> say Pushing the hotfix now, give me 5 minutes
251
+ ```
252
+
253
+ ---
254
+
255
+ ### Shell Commands
256
+
257
+ #### `status`
258
+ Show a full overview of your current shell session: auth status, active room, WebSocket connection, server URL, and version.
259
+
260
+ ```bash
261
+ thinknsh> status
262
+ # ──────────────────────────────────────
263
+ # ThinkNCollab Shell — Session Status
264
+ # ──────────────────────────────────────
265
+ # User : Raman Singh
266
+ # Email : raman@example.com
267
+ # Server : https://app.thinkncollab.com
268
+ # WebSocket : ✅ Connected
269
+ # Active Room: frontend-team (room-abc123)
270
+ # Version : 1.2.0
271
+ # ──────────────────────────────────────
272
+ ```
273
+
274
+ #### `help`
275
+ List all registered commands grouped by category, with descriptions.
276
+
277
+ ```bash
278
+ thinknsh> help
279
+ ```
280
+
281
+ #### `clear`
282
+ Clear the terminal screen.
283
+
284
+ ```bash
285
+ thinknsh> clear
286
+ ```
287
+
288
+ #### `exit` / `quit`
289
+ Exit the interactive shell gracefully.
290
+
291
+ ```bash
292
+ thinknsh> exit
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Single-Command Mode
298
+
299
+ You can run **any command as a one-liner** without entering the interactive shell. The shell executes the command and exits immediately — great for scripting or CI pipelines.
300
+
301
+ ```bash
302
+ thinknsh status
303
+ thinknsh whoami
304
+ thinknsh join room-abc123
305
+ thinknsh say "Deployment complete ✅"
306
+ thinknsh rooms
307
+ ```
308
+
309
+ Multi-word commands work naturally:
310
+
311
+ ```bash
312
+ thinknsh say Build passed. Deploying to staging now.
313
+ thinknsh invite contractor@example.com
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Built-in Shell Features
319
+
320
+ ### Command History
321
+ The interactive shell remembers your previously run commands across sessions. Use the **Up/Down arrow keys** to navigate history.
322
+
323
+ ### Aliases
324
+ Create shortcuts for frequently used commands:
325
+
326
+ ```bash
327
+ thinknsh> alias fe="join room-abc123"
328
+ thinknsh> fe # runs: join room-abc123
329
+ ```
330
+
331
+ ### Shell Variables
332
+ Set and use variables within the shell:
333
+
334
+ ```bash
335
+ thinknsh> set ROOM=room-abc123
336
+ thinknsh> join $ROOM
337
+ ```
338
+
339
+ ### Tab Completion
340
+ Press `Tab` to autocomplete commands and room names.
341
+
342
+ ### Piping & Chaining (Single-command mode)
343
+ Chain commands using `&&` in your terminal:
344
+
345
+ ```bash
346
+ thinknsh login && thinknsh join room-abc123 && thinknsh status
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Examples
352
+
353
+ ```bash
354
+ # Check who you are
355
+ thinknsh whoami
356
+
357
+ # See all your rooms, then join one
358
+ thinknsh rooms
359
+ thinknsh join room-abc123
360
+
361
+ # Send a quick message without opening the shell
362
+ thinknsh say "Hotfix deployed to production"
363
+
364
+ # Invite a new member
365
+ thinknsh invite dev@company.com
366
+
367
+ # Full interactive session
368
+ thinknsh
369
+ thinknsh> login
370
+ thinknsh> rooms
371
+ thinknsh> join frontend-team
372
+ thinknsh> say Good morning everyone 🚀
373
+ thinknsh> teammates
374
+ thinknsh> status
375
+ thinknsh> exit
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Environment Variables
381
+
382
+ | Variable | Default | Description |
383
+ |----------|---------|-------------|
384
+ | `THINKNCOLLAB_SERVER` | `https://thinkncollab.com` | Main server URL (REST + WebSocket base) |
385
+ | `THINKNCOLLAB_API_URL` | `https://thinkncollab.com` | REST API base URL (overrides SERVER for API calls) |
386
+ | `THINKNCOLLAB_WS_URL` | `https://thinkncollab.com` | WebSocket server URL |
387
+ | `THINKNCOLLAB_AUTO_CONNECT` | `false` | Set to `true` to auto-connect WebSocket on shell start |
388
+
389
+ In production, all three URL variables typically point to the same host:
390
+
391
+ ```bash
392
+ export THINKNCOLLAB_SERVER=https://app.thinkncollab.com
393
+ export THINKNCOLLAB_API_URL=https://app.thinkncollab.com
394
+ export THINKNCOLLAB_WS_URL=https://app.thinkncollab.com
395
+ export THINKNCOLLAB_AUTO_CONNECT=true
396
+ ```
397
+
398
+ ---
399
+
400
+ ## License
401
+
402
+ MIT © ThinkNCollab
package/bin/thinknsh.js CHANGED
@@ -46,9 +46,9 @@ if (args.includes('--version') || args.includes('-v')) {
46
46
  }
47
47
 
48
48
  const shell = new ThinkNCollabShell({
49
- serverUrl: process.env.THINKNCOLLAB_SERVER || 'https//thinkncollab.com', // ← /thinknsh hata
50
- apiUrl: process.env.THINKNCOLLAB_API_URL || 'https//thinkncollab.com',
51
- wsUrl: process.env.THINKNCOLLAB_WS_URL || 'https//thinkncollab.com',
49
+ serverUrl: process.env.THINKNCOLLAB_SERVER || 'https://thinkncollab.com', // ← /thinknsh hata
50
+ apiUrl: process.env.THINKNCOLLAB_API_URL || 'https://thinkncollab.com',
51
+ wsUrl: process.env.THINKNCOLLAB_WS_URL || 'https://thinkncollab.com',
52
52
  autoConnect: process.env.THINKNCOLLAB_AUTO_CONNECT === 'true'
53
53
  });
54
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkncollab-shell",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "ThinkNCollab Custom Shell with WebSocket collaboration",
5
5
  "main": "src/core/shell.js",
6
6
  "bin": {
@@ -71,6 +71,7 @@ function printNotification(data) {
71
71
  break;
72
72
  }
73
73
  case 'typing':
74
+ beep();
74
75
  process.stdout.write(`\r${chalk.dim(`✏️ ${data.username} is typing... `)}`);
75
76
  break;
76
77
  case 'stoppedTyping':
package/src/core/shell.js CHANGED
@@ -408,7 +408,7 @@ class ThinkNCollabShell extends EventEmitter {
408
408
  console.log(chalk.cyan(`🔗 Joining: ${args[0]}...`));
409
409
  try {
410
410
  const result = await this.ws.joinRoom(args[0], args[1]);
411
- console.log(chalk.green(`✅ Joined: ${result.roomName || args[0]}`));
411
+ console.log(chalk.green(`Joined: ${result.roomName || args[0]}`));
412
412
  } catch (err) { console.log(chalk.red(`❌ ${err.message}`)); }
413
413
  }
414
414
 
@@ -446,7 +446,7 @@ class ThinkNCollabShell extends EventEmitter {
446
446
  if (!msg) { console.log(chalk.red('Usage: notify <type> <message>')); return; }
447
447
  try {
448
448
  await this.ws.sendNotification({ type, title: 'Shell Notification', message: msg });
449
- console.log(chalk.green('✅ Notification sent'));
449
+ console.log(chalk.green('Notification sent'));
450
450
  } catch (err) { console.log(chalk.red(`Failed: ${err.message}`)); }
451
451
  }
452
452
 
@@ -454,24 +454,24 @@ class ThinkNCollabShell extends EventEmitter {
454
454
  if (!args.length) { for (const [a, c] of this.aliases) console.log(` ${chalk.green(a.padEnd(12))} → ${chalk.dim(c)}`); return; }
455
455
  if (args.length < 2) { console.log(chalk.red('Usage: alias <n> <cmd>')); return; }
456
456
  this.addAlias(args[0], args.slice(1).join(' '));
457
- console.log(chalk.green(`✅ ${args[0]} → ${args.slice(1).join(' ')}`));
457
+ console.log(chalk.green(`${args[0]} → ${args.slice(1).join(' ')}`));
458
458
  }
459
459
 
460
460
  async unaliasCommand(args) {
461
461
  if (!args[0]) { console.log(chalk.red('Usage: unalias <n>')); return; }
462
- this.aliases.delete(args[0]) ? console.log(chalk.green(`✅ Removed: ${args[0]}`)) : console.log(chalk.red(`Not found: ${args[0]}`));
462
+ this.aliases.delete(args[0]) ? console.log(chalk.green(`Removed: ${args[0]}`)) : console.log(chalk.red(`Not found: ${args[0]}`));
463
463
  }
464
464
 
465
465
  async setCommand(args) {
466
466
  if (!args.length) { for (const [k, v] of this.variables) console.log(` ${chalk.green(k.padEnd(12))} = ${v}`); return; }
467
467
  if (args.length < 2) { console.log(chalk.red('Usage: set <n> <value>')); return; }
468
468
  this.variables.set(args[0], args.slice(1).join(' '));
469
- console.log(chalk.green(`✅ ${args[0]} = ${args.slice(1).join(' ')}`));
469
+ console.log(chalk.green(`${args[0]} = ${args.slice(1).join(' ')}`));
470
470
  }
471
471
 
472
472
  async unsetCommand(args) {
473
473
  if (!args[0]) { console.log(chalk.red('Usage: unset <n>')); return; }
474
- this.variables.delete(args[0]) ? console.log(chalk.green(`✅ Removed`)) : console.log(chalk.red(`Not found`));
474
+ this.variables.delete(args[0]) ? console.log(chalk.green(`Removed`)) : console.log(chalk.red(`Not found`));
475
475
  }
476
476
 
477
477
  getSessionPath() { return path.join(this.config.configDir, 'session.json'); }
@@ -509,7 +509,7 @@ class ThinkNCollabShell extends EventEmitter {
509
509
  } catch {}
510
510
  console.log(chalk.cyan(`
511
511
  ╔══════════════════════════════════════════════════════╗
512
- ║ ThinkNCollab Shell v0.0.03 ║
512
+ ║ ThinkNCollab Shell v0.0.05 ║
513
513
  ║ Type 'help' for commands ║
514
514
  ╚══════════════════════════════════════════════════════╝`));
515
515
  }
@@ -19,10 +19,12 @@ class WebSocketManager extends EventEmitter {
19
19
 
20
20
  this.socket = null;
21
21
  this.connected = false;
22
- this.currentUser = null; // { userId, name, userType }
22
+ this.currentUser = null;
23
23
  this.roomId = null;
24
24
  this.reconnectCount = 0;
25
25
  this.messageQueue = [];
26
+ this._authToken = null;
27
+ this._handlersSetup = false;
26
28
  }
27
29
 
28
30
  // ─── Connect ──────────────────────────────────────────────────────────────
@@ -30,7 +32,8 @@ class WebSocketManager extends EventEmitter {
30
32
  connect(authToken = null) {
31
33
  return new Promise((resolve, reject) => {
32
34
  try {
33
- // Connect to /thinknsh namespace — isolated from web
35
+ this._authToken = authToken;
36
+
34
37
  this.socket = io(this.config.serverUrl + '/thinknsh', {
35
38
  transports: ['websocket', 'polling'],
36
39
  reconnection: true,
@@ -45,9 +48,7 @@ class WebSocketManager extends EventEmitter {
45
48
  clearTimeout(timeout);
46
49
  this.connected = true;
47
50
  this.reconnectCount = 0;
48
- this._authToken = authToken;
49
51
 
50
- // Announce terminal to backend — join personal notification room
51
52
  if (this.currentUser) {
52
53
  console.log('🖥️ Emitting thinknsh:connected for', this.currentUser.userId);
53
54
  this.socket.emit('thinknsh:connected', {
@@ -61,6 +62,10 @@ class WebSocketManager extends EventEmitter {
61
62
  this.emit('connected', { socketId: this.socket.id });
62
63
  resolve({ success: true, socketId: this.socket.id });
63
64
  });
65
+ // One-time listener for confirmation
66
+ // this.ws.once('notified', (data) => {
67
+ // console.log(chalk.green(`✅ Notification sent to ${data.sentTo} member(s) in "${data.roomName}"`));
68
+ // });
64
69
 
65
70
  this.socket.on('connect_error', (error) => {
66
71
  clearTimeout(timeout);
@@ -79,13 +84,27 @@ class WebSocketManager extends EventEmitter {
79
84
  this.socket.on('reconnect', (attemptNumber) => {
80
85
  this.connected = true;
81
86
  this.emit('reconnected', { attemptNumber });
87
+
88
+ // Re-announce user after reconnect
89
+ if (this.currentUser) {
90
+ this.socket.emit('thinknsh:connected', {
91
+ userId: this.currentUser.userId,
92
+ userName: this.currentUser.name,
93
+ shellToken: this._authToken,
94
+ });
95
+ }
96
+
82
97
  // Rejoin room after reconnect
83
98
  if (this.roomId && this.currentUser) {
84
99
  this._emitJoin(this.roomId);
85
100
  }
86
101
  });
87
102
 
88
- this.setupEventHandlers(authToken);
103
+ // ✅ Setup handlers only ONCE per socket instance
104
+ if (!this._handlersSetup) {
105
+ this.setupEventHandlers();
106
+ this._handlersSetup = true;
107
+ }
89
108
 
90
109
  } catch (error) {
91
110
  reject(error);
@@ -93,33 +112,39 @@ class WebSocketManager extends EventEmitter {
93
112
  });
94
113
  }
95
114
 
96
- // ─── thinknsh: event handlers ─────────────────────────────────────────────
115
+ // ─── Event Handlers (registered once) ────────────────────────────────────
97
116
 
98
- setupEventHandlers(authToken) {
117
+ setupEventHandlers() {
99
118
 
100
119
  // Terminal ready confirmed by server
101
120
  this.socket.on('thinknsh:ready', (data) => {
102
121
  this.emit('ready', data);
103
122
  });
104
123
 
105
- // Room join confirmed
124
+ // ✅ Room join confirmed + history delivered
106
125
  this.socket.on('thinknsh:joined', (data) => {
107
126
  this.roomId = data.roomId;
127
+
128
+ // Emit history separately so shell.js can display it
129
+ if (data.history && data.history.length > 0) {
130
+ this.emit('messageHistory', data.history);
131
+ }
132
+
108
133
  this.emit('roomJoined', data);
109
134
  });
110
135
 
111
- // Incoming message from terminal or web
136
+ // Incoming message from terminal users
112
137
  this.socket.on('thinknsh:message', (data) => {
113
138
  this.emit('message', {
114
139
  username: data.name,
115
140
  userId: data.userId,
116
141
  message: data.message,
117
- source: data.source || 'web',
142
+ source: data.source || 'shell',
118
143
  timestamp: data.timestamp,
119
144
  });
120
145
  });
121
146
 
122
- // Also listen to standard chat-message (from web users)
147
+ // Incoming message from web users
123
148
  this.socket.on('chat-message', (data) => {
124
149
  this.emit('message', {
125
150
  username: data.name,
@@ -135,14 +160,19 @@ class WebSocketManager extends EventEmitter {
135
160
  this.emit('userJoined', { username: data.name, source: 'shell' });
136
161
  });
137
162
 
163
+ // ✅ User left from terminal
164
+ this.socket.on('thinknsh:userLeft', (data) => {
165
+ this.emit('userLeft', { username: data.name, source: 'shell' });
166
+ });
167
+
138
168
  // User joined from web
139
169
  this.socket.on('user-joined', (name) => {
140
170
  this.emit('userJoined', { username: name, source: 'web' });
141
171
  });
142
172
 
143
- // User left
173
+ // User left from web
144
174
  this.socket.on('user-left', (name) => {
145
- this.emit('userLeft', { username: name });
175
+ this.emit('userLeft', { username: name, source: 'web' });
146
176
  });
147
177
 
148
178
  // User list updated
@@ -150,40 +180,50 @@ class WebSocketManager extends EventEmitter {
150
180
  this.emit('userList', { users });
151
181
  });
152
182
 
153
- // Notifications from NotificationEngine
154
- this.socket.on('new-notification', (notification) => {
155
- this.emit('notification', {
156
- type: notification.type || 'info',
157
- title: notification.type || 'Notification',
158
- message: notification.message || '',
159
- meta: notification.meta || {},
160
- });
161
- });
162
-
163
- // Terminal status (online/offline) — website sends this
164
- this.socket.on('thinknsh:status', (data) => {
165
- this.emit('shellStatus', data);
183
+ // ✅ Manual history fetch response
184
+ this.socket.on('thinknsh:history', (data) => {
185
+ if (data.messages && data.messages.length > 0) {
186
+ this.emit('messageHistory', data.messages);
187
+ } else {
188
+ this.emit('messageHistory', []);
189
+ }
166
190
  });
167
191
 
168
- // ── new-notification from NotificationEngine → forward to TCP window ──
192
+ // Single new-notification listener
169
193
  this.socket.on('new-notification', (data) => {
170
- // This goes to shell.js pushNotification → TCP → notification window
171
194
  this.emit('notification', {
172
- type: data.type || 'notification',
173
- level: data.type || 'info',
174
- title: data.type || 'Notification',
175
- message: data.message || '',
195
+ type: data.type || 'notification',
196
+ level: data.type || 'info',
197
+ title: data.type || 'Notification',
198
+ message: data.message || '',
199
+ meta: data.meta || {},
176
200
  taskTitle: data.taskTitle || data.meta?.taskTitle || '',
177
201
  assignedBy: data.assignedBy || data.meta?.assignedBy || '',
178
202
  });
179
203
  });
180
204
 
205
+ // Room activity feed
206
+ this.socket.on('room:activity', (data) => {
207
+ this.emit('roomActivity', {
208
+ action: data.action,
209
+ label: data.label,
210
+ userName: data.userName,
211
+ meta: data.meta || {},
212
+ createdAt: data.createdAt || new Date(),
213
+ });
214
+ });
215
+
216
+ // Terminal status (online/offline)
217
+ this.socket.on('thinknsh:status', (data) => {
218
+ this.emit('shellStatus', data);
219
+ });
220
+
181
221
  // Notification list response
182
222
  this.socket.on('thinknsh:notifications', (data) => {
183
223
  this.emit('notificationList', data.notifications);
184
224
  });
185
225
 
186
- // Error from server
226
+ // Server errors
187
227
  this.socket.on('thinknsh:error', (data) => {
188
228
  this.emit('serverError', data);
189
229
  });
@@ -197,11 +237,13 @@ class WebSocketManager extends EventEmitter {
197
237
 
198
238
  setUser(user, token = null) {
199
239
  this.currentUser = user;
200
- // If already connected, announce now
240
+ if (token) this._authToken = token;
241
+
201
242
  if (this.connected && this.socket) {
202
243
  this.socket.emit('thinknsh:connected', {
203
244
  userId: user.userId,
204
- shellToken: token,
245
+ userName: user.name,
246
+ shellToken: token || this._authToken,
205
247
  });
206
248
  }
207
249
  }
@@ -265,13 +307,48 @@ class WebSocketManager extends EventEmitter {
265
307
  return Promise.resolve({ success: true });
266
308
  }
267
309
 
268
- // ─── Get unread notifications ─────────────────────────────────────────────
310
+ // ─── Fetch message history manually ──────────────────────────────────────
311
+
312
+ getHistory(limit = 30, skip = 0) {
313
+ if (!this.connected || !this.socket) return;
314
+ if (!this.roomId) return;
315
+
316
+ this.socket.emit('thinknsh:getHistory', {
317
+ roomId: this.roomId,
318
+ limit,
319
+ skip,
320
+ });
321
+ }
322
+
323
+ // ─── Notifications ────────────────────────────────────────────────────────
269
324
 
270
325
  getNotifications() {
271
326
  if (!this.connected || !this.currentUser) return;
272
- this.socket.emit('thinknsh:getNotifications', { userId: this.currentUser.userId });
327
+ this.socket.emit('thinknsh:getNotifications', {
328
+ userId: this.currentUser.userId
329
+ });
330
+ }
331
+
332
+ // ─── Send notification ────────────────────────────────────────────────────
333
+
334
+ sendNotification(notification, target = 'room') {
335
+ if (!this.connected || !this.socket) {
336
+ return Promise.reject(new Error('Not connected'));
273
337
  }
274
338
 
339
+ const payload = {
340
+ type: notification.type || 'info',
341
+ title: notification.title || 'Notification',
342
+ message: notification.message,
343
+ data: notification.data || {},
344
+ target: target === 'room' ? this.roomId : target,
345
+ timestamp: new Date().toISOString()
346
+ };
347
+
348
+ this.socket.emit('notification:send', payload);
349
+ return Promise.resolve({ success: true });
350
+ }
351
+
275
352
  markNotificationsRead(notificationIds = []) {
276
353
  if (!this.connected || !this.currentUser) return;
277
354
  this.socket.emit('thinknsh:markRead', {
@@ -287,12 +364,10 @@ class WebSocketManager extends EventEmitter {
287
364
  this.socket.emit('thinknsh:connected', {
288
365
  userId,
289
366
  userName: this.currentUser?.name || '',
290
- shellToken: this._token || null,
367
+ shellToken: this._authToken || null,
291
368
  });
292
369
  }
293
370
 
294
- sendTyping() {} // add later if needed
295
-
296
371
  ping() {
297
372
  if (this.connected && this.socket) this.socket.emit('ping');
298
373
  }
@@ -304,19 +379,22 @@ class WebSocketManager extends EventEmitter {
304
379
  }
305
380
  }
306
381
 
382
+ sendTyping() {} // add later if needed
383
+
307
384
  disconnect() {
308
385
  if (this.socket) {
309
386
  this.socket.disconnect();
310
- this.socket = null;
311
- this.connected = false;
312
- this.currentUser = null;
313
- this.roomId = null;
387
+ this.socket = null;
388
+ this.connected = false;
389
+ this.currentUser = null;
390
+ this.roomId = null;
391
+ this._handlersSetup = false;
314
392
  }
315
393
  }
316
394
 
317
- isConnected() { return this.connected && !!this.socket?.connected; }
318
- getCurrentRoom() { return this.roomId; }
319
- getSocketId() { return this.socket?.id || null; }
395
+ isConnected() { return this.connected && !!this.socket?.connected; }
396
+ getCurrentRoom() { return this.roomId; }
397
+ getSocketId() { return this.socket?.id || null; }
320
398
  getParticipants() { return Promise.resolve([]); }
321
399
  }
322
400