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 +392 -16
- package/bin/thinknsh.js +3 -3
- package/package.json +1 -1
- package/src/core/notification-window.js +1 -0
- package/src/core/shell.js +7 -7
- package/src/core/websocket.js +125 -47
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**
|
|
8
|
-
- 🔌 **WebSocket Integration**
|
|
9
|
-
- 👥 **Room Collaboration**
|
|
10
|
-
- 💬 **Messaging**
|
|
11
|
-
- 🔔 **Notifications**
|
|
12
|
-
- 📁 **File System**
|
|
13
|
-
- ⚡ **Command System**
|
|
14
|
-
- 🔧 **Aliases**
|
|
15
|
-
- 📊 **Variables**
|
|
16
|
-
- 📜 **History**
|
|
17
|
-
- 🎨 **Colored 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
|
|
50
|
-
apiUrl: process.env.THINKNCOLLAB_API_URL || 'https
|
|
51
|
-
wsUrl: process.env.THINKNCOLLAB_WS_URL || 'https
|
|
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
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(
|
|
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('
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
512
|
+
║ ThinkNCollab Shell v0.0.05 ║
|
|
513
513
|
║ Type 'help' for commands ║
|
|
514
514
|
╚══════════════════════════════════════════════════════╝`));
|
|
515
515
|
}
|
package/src/core/websocket.js
CHANGED
|
@@ -19,10 +19,12 @@ class WebSocketManager extends EventEmitter {
|
|
|
19
19
|
|
|
20
20
|
this.socket = null;
|
|
21
21
|
this.connected = false;
|
|
22
|
-
this.currentUser = null;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
// ───
|
|
115
|
+
// ─── Event Handlers (registered once) ────────────────────────────────────
|
|
97
116
|
|
|
98
|
-
setupEventHandlers(
|
|
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
|
|
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 || '
|
|
142
|
+
source: data.source || 'shell',
|
|
118
143
|
timestamp: data.timestamp,
|
|
119
144
|
});
|
|
120
145
|
});
|
|
121
146
|
|
|
122
|
-
//
|
|
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
|
-
//
|
|
154
|
-
this.socket.on('
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
//
|
|
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:
|
|
173
|
-
level:
|
|
174
|
-
title:
|
|
175
|
-
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
// ───
|
|
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', {
|
|
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.
|
|
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
|
|
311
|
-
this.connected
|
|
312
|
-
this.currentUser
|
|
313
|
-
this.roomId
|
|
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()
|
|
318
|
-
getCurrentRoom()
|
|
319
|
-
getSocketId()
|
|
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
|
|