thinkncollab-shell 0.0.19 → 2.0.0-beta.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.
Files changed (201) hide show
  1. package/LICENSE +82 -0
  2. package/Readme.md +246 -502
  3. package/dist/bin/thinknsh.js +8 -0
  4. package/dist/src/commands/auth/login.js +7 -0
  5. package/dist/src/commands/auth/logout.js +7 -0
  6. package/dist/src/commands/auth/register.js +7 -0
  7. package/dist/src/commands/auth/whoami.js +7 -0
  8. package/dist/src/commands/dock/bench.js +7 -0
  9. package/dist/src/commands/dock/ci.js +7 -0
  10. package/dist/src/commands/dock/cluster.js +7 -0
  11. package/dist/src/commands/dock/containers.js +7 -0
  12. package/dist/src/commands/dock/dock.js +7 -0
  13. package/dist/src/commands/github/commits.js +7 -0
  14. package/dist/src/commands/github/issue-create.js +7 -0
  15. package/dist/src/commands/github/issues.js +7 -0
  16. package/dist/src/commands/github/pulls.js +7 -0
  17. package/dist/src/commands/github/statusgh.js +7 -0
  18. package/dist/src/commands/index.js +7 -0
  19. package/dist/src/commands/message/say.js +7 -0
  20. package/dist/src/commands/room/join.js +7 -0
  21. package/dist/src/commands/room/leave.js +7 -0
  22. package/dist/src/commands/room/rooms.js +7 -0
  23. package/dist/src/commands/room/sync.js +7 -0
  24. package/dist/src/commands/sandbox/console.js +7 -0
  25. package/dist/src/commands/sandbox/dep.js +7 -0
  26. package/dist/src/commands/sandbox/deploy.js +7 -0
  27. package/dist/src/commands/sandbox/dev.js +7 -0
  28. package/dist/src/commands/sandbox/exec.js +7 -0
  29. package/dist/src/commands/sandbox/freeze.js +7 -0
  30. package/dist/src/commands/sandbox/push.js +7 -0
  31. package/dist/src/commands/sandbox/resume.js +7 -0
  32. package/dist/src/commands/sandbox/sandbox.js +7 -0
  33. package/dist/src/commands/sandbox/share.js +7 -0
  34. package/dist/src/commands/sandbox/snapshot.js +7 -0
  35. package/dist/src/commands/sandbox/sync-deps.js +7 -0
  36. package/dist/src/commands/shell/doctor.js +7 -0
  37. package/dist/src/commands/shell/notifications.js +7 -0
  38. package/dist/src/commands/shell/notify.js +7 -0
  39. package/dist/src/commands/shell/status.js +7 -0
  40. package/dist/src/commands/shell/version.js +7 -0
  41. package/dist/src/commands/tasks/build.js +7 -0
  42. package/dist/src/commands/tasks/complete.js +7 -0
  43. package/dist/src/commands/tasks/create-task.js +7 -0
  44. package/dist/src/commands/tasks/loadtest.js +7 -0
  45. package/dist/src/commands/tasks/task-status.js +7 -0
  46. package/dist/src/commands/tasks/task.js +7 -0
  47. package/dist/src/commands/tasks/tasks.js +7 -0
  48. package/dist/src/commands/team/invite.js +7 -0
  49. package/dist/src/commands/team/myteam.js +7 -0
  50. package/dist/src/commands/team/teammates.js +7 -0
  51. package/dist/src/commands/terminal/share.js +7 -0
  52. package/dist/src/core/api-client.js +7 -0
  53. package/dist/src/core/crypto/e2ee-vault.js +7 -0
  54. package/dist/src/core/dep-store.js +7 -0
  55. package/dist/src/core/dependency-tracker.js +7 -0
  56. package/dist/src/core/geekload.js +7 -0
  57. package/dist/src/core/local-runner.js +7 -0
  58. package/dist/src/core/multi-lang-scanner.js +7 -0
  59. package/dist/src/core/notification-window.js +8 -0
  60. package/dist/src/core/notify-auth.js +7 -0
  61. package/dist/src/core/project-config.js +7 -0
  62. package/dist/src/core/sandbox/arch-shim.js +7 -0
  63. package/dist/src/core/sandbox/capsule-builder.js +7 -0
  64. package/dist/src/core/sandbox/cgroups.js +7 -0
  65. package/dist/src/core/sandbox/container-monitor.js +7 -0
  66. package/dist/src/core/sandbox/crun-manager.js +7 -0
  67. package/dist/src/core/sandbox/engine.js +7 -0
  68. package/dist/src/core/sandbox/image-manager.js +7 -0
  69. package/dist/src/core/sandbox/image-store.js +7 -0
  70. package/dist/src/core/sandbox/index.js +7 -0
  71. package/dist/src/core/sandbox/macos-sandbox.js +7 -0
  72. package/dist/src/core/sandbox/microvm-manager.js +7 -0
  73. package/dist/src/core/sandbox/native-service-manager.js +7 -0
  74. package/dist/src/core/sandbox/network.js +7 -0
  75. package/dist/src/core/sandbox/oci-exporter.js +7 -0
  76. package/dist/src/core/sandbox/oci-puller.js +7 -0
  77. package/dist/src/core/sandbox/oci-runtime-spec.js +7 -0
  78. package/dist/src/core/sandbox/policy-engine.js +7 -0
  79. package/dist/src/core/sandbox/port-proxy-daemon.js +7 -0
  80. package/dist/src/core/sandbox/room-sync-manager.js +7 -0
  81. package/dist/src/core/sandbox/runtime-manager.js +7 -0
  82. package/dist/src/core/sandbox/snapshot-manager.js +7 -0
  83. package/dist/src/core/sandbox/storage.js +7 -0
  84. package/dist/src/core/sandbox/supervisor.js +7 -0
  85. package/dist/src/core/sandbox/tunnel-daemon.js +7 -0
  86. package/dist/src/core/sandbox/tunnel.js +7 -0
  87. package/dist/src/core/sandbox/window-launcher.js +7 -0
  88. package/dist/src/core/sandbox/windows-wsl2.js +7 -0
  89. package/dist/src/core/sandbox-window.js +8 -0
  90. package/dist/src/core/shell.js +7 -0
  91. package/dist/src/core/websocket.js +7 -0
  92. package/dist/src/dock/api/routes.js +7 -0
  93. package/dist/src/dock/api/server.js +7 -0
  94. package/dist/src/dock/api/wire.js +7 -0
  95. package/dist/src/dock/bench/memory.js +7 -0
  96. package/dist/src/dock/bench/runner.js +7 -0
  97. package/dist/src/dock/build/builder.js +7 -0
  98. package/dist/src/dock/build/buildfile.js +7 -0
  99. package/dist/src/dock/build/buildkit.js +7 -0
  100. package/dist/src/dock/build/changes.js +7 -0
  101. package/dist/src/dock/ci/actions.js +7 -0
  102. package/dist/src/dock/ci/runner.js +7 -0
  103. package/dist/src/dock/ci/workflow.js +7 -0
  104. package/dist/src/dock/cli/api.js +7 -0
  105. package/dist/src/dock/cli/args.js +7 -0
  106. package/dist/src/dock/cli/commands.js +7 -0
  107. package/dist/src/dock/cli/compose.js +7 -0
  108. package/dist/src/dock/cli/context.js +7 -0
  109. package/dist/src/dock/cli/files.js +7 -0
  110. package/dist/src/dock/cli/format.js +7 -0
  111. package/dist/src/dock/cli/manage.js +7 -0
  112. package/dist/src/dock/cli/moved.js +7 -0
  113. package/dist/src/dock/cli/services.js +7 -0
  114. package/dist/src/dock/cli/session.js +7 -0
  115. package/dist/src/dock/cli/workspace.js +7 -0
  116. package/dist/src/dock/client.js +7 -0
  117. package/dist/src/dock/compat/cases.js +7 -0
  118. package/dist/src/dock/compat/runner.js +7 -0
  119. package/dist/src/dock/config.js +7 -0
  120. package/dist/src/dock/daemon-main.js +7 -0
  121. package/dist/src/dock/daemon.js +7 -0
  122. package/dist/src/dock/deploy/compose.js +7 -0
  123. package/dist/src/dock/engine.js +7 -0
  124. package/dist/src/dock/image/archive.js +7 -0
  125. package/dist/src/dock/image/auth.js +7 -0
  126. package/dist/src/dock/image/reference.js +7 -0
  127. package/dist/src/dock/image/registry.js +7 -0
  128. package/dist/src/dock/image/tar.js +7 -0
  129. package/dist/src/dock/index.js +7 -0
  130. package/dist/src/dock/linux/host.js +7 -0
  131. package/dist/src/dock/paths.js +7 -0
  132. package/dist/src/dock/runtime/emulation.js +7 -0
  133. package/dist/src/dock/runtime/fsevents.js +7 -0
  134. package/dist/src/dock/runtime/fsops.js +7 -0
  135. package/dist/src/dock/runtime/hostfwd.js +7 -0
  136. package/dist/src/dock/runtime/logs.js +7 -0
  137. package/dist/src/dock/runtime/network.js +7 -0
  138. package/dist/src/dock/runtime/ports.js +7 -0
  139. package/dist/src/dock/runtime/rootfs.js +7 -0
  140. package/dist/src/dock/runtime/seccomp-profile.LICENSE +202 -0
  141. package/dist/src/dock/runtime/seccomp-profile.json +1227 -0
  142. package/dist/src/dock/runtime/seccomp.js +7 -0
  143. package/dist/src/dock/runtime/spec.js +7 -0
  144. package/dist/src/dock/runtime/stats.js +7 -0
  145. package/dist/src/dock/runtime/volumes.js +7 -0
  146. package/dist/src/dock/selftest.js +7 -0
  147. package/dist/src/dock/setup/app.js +7 -0
  148. package/dist/src/dock/setup/autostart.js +7 -0
  149. package/dist/src/dock/setup/index.js +7 -0
  150. package/dist/src/dock/setup/integrate.js +7 -0
  151. package/dist/src/dock/setup/migrate.js +7 -0
  152. package/dist/src/dock/state/store.js +7 -0
  153. package/dist/src/dock/ui/page.js +385 -0
  154. package/dist/src/dock/ui/server.js +7 -0
  155. package/dist/src/dock/version.js +7 -0
  156. package/dist/src/dock/workspace/apps.js +7 -0
  157. package/dist/src/dock/workspace/workspace.js +7 -0
  158. package/dist/src/events/notifications.js +7 -0
  159. package/dist/src/utils/logger.js +7 -0
  160. package/dist/src/utils/terminalCrypto.js +7 -0
  161. package/package.json +41 -16
  162. package/bin/thinknsh.js +0 -71
  163. package/src/commands/auth/login.js +0 -84
  164. package/src/commands/auth/logout.js +0 -32
  165. package/src/commands/auth/register.js +0 -52
  166. package/src/commands/auth/whoami.js +0 -52
  167. package/src/commands/github/commits.js +0 -58
  168. package/src/commands/github/issue-create.js +0 -59
  169. package/src/commands/github/issues.js +0 -57
  170. package/src/commands/github/pulls.js +0 -58
  171. package/src/commands/github/statusgh.js +0 -50
  172. package/src/commands/index.js +0 -136
  173. package/src/commands/message/say.js +0 -49
  174. package/src/commands/room/join.js +0 -142
  175. package/src/commands/room/leave.js +0 -24
  176. package/src/commands/room/rooms.js +0 -66
  177. package/src/commands/shell/notify.js +0 -88
  178. package/src/commands/shell/status.js +0 -73
  179. package/src/commands/shell/version.js +0 -18
  180. package/src/commands/tasks/build.js +0 -190
  181. package/src/commands/tasks/complete.js +0 -940
  182. package/src/commands/tasks/create-task.js +0 -101
  183. package/src/commands/tasks/loadtest.js +0 -101
  184. package/src/commands/tasks/suggestTask.js +0 -0
  185. package/src/commands/tasks/task-status.js +0 -72
  186. package/src/commands/tasks/task.js +0 -182
  187. package/src/commands/tasks/tasks.js +0 -184
  188. package/src/commands/team/invite.js +0 -46
  189. package/src/commands/team/myteam.js +0 -78
  190. package/src/commands/team/teammates.js +0 -52
  191. package/src/commands/terminal/share.js +0 -261
  192. package/src/core/api-client.js +0 -246
  193. package/src/core/geekload.js +0 -524
  194. package/src/core/local-runner.js +0 -861
  195. package/src/core/notification-window.js +0 -165
  196. package/src/core/project-config.js +0 -100
  197. package/src/core/shell.js +0 -734
  198. package/src/core/websocket.js +0 -482
  199. package/src/events/notifications.js +0 -184
  200. package/src/utils/logger.js +0 -159
  201. package/src/utils/terminalCrypto.js +0 -47
@@ -1,482 +0,0 @@
1
- /*
2
- src/core/websocket.js — WebSocket Manager for ThinkNCollab Shell
3
- */
4
-
5
- const { io } = require('socket.io-client');
6
- const EventEmitter = require('events');
7
-
8
- class WebSocketManager extends EventEmitter {
9
-
10
- constructor(config = {}) {
11
- super();
12
- this.config = {
13
- serverUrl: config.serverUrl || 'https://thinkncollab.com',
14
- reconnectAttempts: config.reconnectAttempts || 5,
15
- reconnectDelay: config.reconnectDelay || 1000,
16
- ...config
17
- };
18
-
19
- this.socket = null;
20
- this.connected = false;
21
- this.currentUser = null; // always { userId, name, userType }
22
- this.roomId = null;
23
- this.reconnectCount = 0;
24
- this.messageQueue = [];
25
- this._authToken = null;
26
- this._handlersSetup = false;
27
- this._serverReady = false;
28
- this._shellRef = config._shellRef || null; // ref to Shell instance for api access
29
- }
30
-
31
- // ─── Connect ──────────────────────────────────────────────────────────────
32
-
33
- connect(authToken = null) {
34
- return new Promise((resolve, reject) => {
35
- try {
36
- if (authToken) this._authToken = authToken;
37
-
38
- this.socket = io(this.config.serverUrl + '/thinknsh', {
39
- transports: ['websocket', 'polling'],
40
- reconnection: true,
41
- reconnectionAttempts: this.config.reconnectAttempts,
42
- reconnectionDelay: this.config.reconnectDelay,
43
- auth: this._authToken ? { token: this._authToken } : {},
44
- });
45
-
46
- const timeout = setTimeout(() => {
47
- reject(new Error('Connection timeout'));
48
- }, 10000);
49
-
50
- this.socket.on('connect', () => {
51
- clearTimeout(timeout);
52
- this.connected = true;
53
- this.reconnectCount = 0;
54
-
55
- // Announce user immediately on connect if we have one
56
- if (this.currentUser) {
57
- this._announceUser();
58
- }
59
-
60
- this.processPendingMessages();
61
- this.emit('connected', { socketId: this.socket.id });
62
- resolve({ success: true, socketId: this.socket.id });
63
- });
64
-
65
- this.socket.on('connect_error', (error) => {
66
- clearTimeout(timeout);
67
- if (this.reconnectCount === 0) {
68
- reject(new Error(`Cannot connect to ${this.config.serverUrl}`));
69
- }
70
- this.reconnectCount++;
71
- this.emit('error', { message: error.message });
72
- });
73
-
74
- this.socket.on('disconnect', (reason) => {
75
- this.connected = false;
76
- this.emit('disconnected', { reason });
77
- });
78
- this.socket.on('task:verdict', (data) => {
79
- this.emit('task:verdict', data);
80
- });
81
-
82
- // Triggered by TNC server when user clicks "run test" on browser (production)
83
- // CLI reads .tnc/workflows/*.yml from process.cwd() (client's project dir) and runs it locally
84
- this.socket.on('task:run-local', async (data) => {
85
- try {
86
- const chalk = require('chalk');
87
- const { runLocal } = require('./local-runner');
88
- const taskId = data.taskId;
89
- const shellRef = this._shellRef;
90
- const api = shellRef ? shellRef.api : null;
91
-
92
- if (!api) {
93
- console.log(chalk.red('\n❌ CLI not authenticated. Run: login\n'));
94
- return;
95
- }
96
-
97
- // Fetch task info (webhookSecret) from TNC API
98
- let task;
99
- try {
100
- task = await api._request('GET', `/thinknsh/${taskId}/cli`);
101
- } catch (e) {
102
- console.log(chalk.red(`\n❌ Could not fetch task info: ${e.message}\n`));
103
- return;
104
- }
105
-
106
- if (!task || !task.webhookSecret) {
107
- console.log(chalk.red('\n❌ Task has no webhookSecret — cannot submit result.\n'));
108
- return;
109
- }
110
-
111
- await runLocal({
112
- taskId,
113
- webhookSecret: task.webhookSecret,
114
- apiUrl: this.config.serverUrl || 'https://thinkncollab.com',
115
- // Pass repo info from TNC task — runner will clone this exact repo
116
- repoUrl: task.repoUrl || task.githubUrl || task.repo_url || null,
117
- repoBranch: task.branch || task.repoBranch || 'main',
118
- workflowFile: task.workflowFile || task.workflow || null,
119
- chalk,
120
- shell: shellRef,
121
- onLog: (logData) => {
122
- // Emit log stream to server via the terminal socket
123
- this.socket.emit('task:log', {
124
- taskId,
125
- boardId: task.boardId,
126
- ...logData
127
- });
128
- }
129
- });
130
-
131
- } catch (err) {
132
- console.error('task:run-local error:', err.message);
133
- }
134
- });
135
-
136
- this.socket.on('reconnect', () => {
137
- this.connected = true;
138
- this.emit('reconnected', {});
139
-
140
- if (this.currentUser) this._announceUser();
141
- if (this.roomId && this.currentUser) this._emitJoin(this.roomId);
142
- });
143
-
144
- if (!this._handlersSetup) {
145
- this.setupEventHandlers();
146
- this._handlersSetup = true;
147
- }
148
-
149
- } catch (error) {
150
- reject(error);
151
- }
152
- });
153
- }
154
-
155
- // ─── Announce user to server (thinknsh:connected) ─────────────────────────
156
-
157
- _announceUser() {
158
- if (!this.socket || !this.currentUser) return;
159
- this.socket.emit('thinknsh:connected', {
160
- userId: this.currentUser.userId,
161
- userName: this.currentUser.name,
162
- shellToken: this._authToken,
163
- });
164
- }
165
-
166
- // ─── Set user — called right after login ──────────────────────────────────
167
-
168
- /**
169
- * Accepts any of these shapes and normalises to { userId, name, userType }:
170
- * { _id, userId, name, email, userType } ← from api-client.getUser()
171
- * { userId, name, userType } ← legacy
172
- */
173
- setUser(user, token = null) {
174
- this._user = user;
175
- if (!user) return;
176
-
177
- if (token) this._authToken = token;
178
-
179
- // Normalise — support both _id and userId
180
- const id = (user.userId || user._id || '').toString();
181
- this.currentUser = {
182
- userId: id,
183
- name: user.name || user.email || 'Unknown',
184
- userType: user.userType || user.model || user.role || 'User',
185
- };
186
-
187
- // If already connected, announce immediately
188
- if (this.connected && this.socket) {
189
- this._announceUser();
190
- }
191
- }
192
-
193
- // ─── Event Handlers ───────────────────────────────────────────────────────
194
-
195
- setupEventHandlers() {
196
-
197
- this.socket.on('thinknsh:ready', (data) => {
198
- this._serverReady = true;
199
- this.emit('ready', data);
200
- });
201
-
202
- this.socket.on('thinknsh:joined', (data) => {
203
- this.roomId = data.roomId;
204
- if (data.history?.length) this.emit('messageHistory', data.history);
205
- this.emit('roomJoined', data);
206
- });
207
-
208
- this.socket.on('thinknsh:message', (data) => {
209
- this.emit('message', {
210
- username: data.name,
211
- userId: data.userId,
212
- message: data.message,
213
- source: 'shell',
214
- timestamp: data.timestamp,
215
- });
216
- });
217
-
218
- this.socket.on('chat-message', (data) => {
219
- this.emit('message', {
220
- username: data.name,
221
- userId: data.userId,
222
- message: data.message,
223
- source: data.source || 'web',
224
- timestamp: data.timestamp,
225
- });
226
- });
227
-
228
- this.socket.on('thinknsh:userJoined', (data) => {
229
- this.emit('userJoined', { username: data.name, source: 'shell' });
230
- });
231
-
232
- this.socket.on('thinknsh:userLeft', (data) => {
233
- this.emit('userLeft', { username: data.name, source: 'shell' });
234
- });
235
-
236
- this.socket.on('user-joined', (name) => {
237
- this.emit('userJoined', { username: name, source: 'web' });
238
- });
239
-
240
- this.socket.on('user-left', (name) => {
241
- this.emit('userLeft', { username: name, source: 'web' });
242
- });
243
-
244
- this.socket.on('user-list', (users) => {
245
- this.emit('userList', { users });
246
- });
247
-
248
- this.socket.on('thinknsh:history', (data) => {
249
- this.emit('messageHistory', data.messages?.length ? data.messages : []);
250
- });
251
-
252
- this.socket.on('new-notification', (data) => {
253
- this.emit('notification', {
254
- type: data.type || 'info',
255
- level: data.type || 'info',
256
- title: data.title || 'Notification',
257
- message: data.message || '',
258
- meta: data.meta || {},
259
- taskTitle: data.taskTitle || data.meta?.taskTitle || '',
260
- assignedBy: data.assignedBy || data.meta?.assignedBy || '',
261
- });
262
- });
263
-
264
- this.socket.on('room:activity', (data) => {
265
- this.emit('roomActivity', data);
266
- });
267
-
268
- this.socket.on('thinknsh:status', (data) => {
269
- this.emit('shellStatus', data);
270
- });
271
-
272
- this.socket.on('thinknsh:notifications', (data) => {
273
- this.emit('notificationList', data.notifications);
274
- });
275
-
276
- // Server errors — emit as 'serverError' so shell.js can handle separately
277
- this.socket.on('thinknsh:error', (data) => {
278
- this.emit('serverError', data);
279
- });
280
-
281
- this.socket.on('error', (data) => {
282
- this.emit('serverError', data);
283
- });
284
-
285
- // Triggered by TNC server when user clicks "Run in Browser" or triggers CI/CD pipeline
286
- const activeRunIds = new Set();
287
- const handleCicdRun = async (data) => {
288
- if (!data || !data.runId) return;
289
- if (activeRunIds.has(data.runId)) return;
290
- activeRunIds.add(data.runId);
291
-
292
- try {
293
- const chalk = require('chalk');
294
- const { runCicdLocal } = require('./local-runner');
295
- const shellRef = this._shellRef;
296
- const api = shellRef ? shellRef.api : null;
297
-
298
- console.log(chalk.cyan(`\n🚀 [thinkncollab-shell] CI/CD execution request received for room: ${data.roomId || this.roomId}`));
299
-
300
- await runCicdLocal({
301
- runId: data.runId,
302
- roomId: data.roomId || this.roomId,
303
- workflowFile: data.workflowFile || 'universal-polyglot-ci.yml',
304
- workflowYaml: data.workflowYaml || '',
305
- localPath: data.localPath || '',
306
- apiUrl: api ? api.baseURL : this.config.serverUrl,
307
- chalk,
308
- api,
309
- socket: this.socket
310
- });
311
- } catch (err) {
312
- console.error('\n❌ Error in CI/CD local execution:', err);
313
- } finally {
314
- setTimeout(() => activeRunIds.delete(data.runId), 30000);
315
- }
316
- };
317
-
318
- this.socket.on('cicd:run-dispatch', handleCicdRun);
319
- }
320
-
321
- // ─── Join room ────────────────────────────────────────────────────────────
322
-
323
- joinRoom(roomId) {
324
- return new Promise((resolve, reject) => {
325
- if (!this.connected || !this.socket)
326
- return reject(new Error('Not connected to server'));
327
- if (!this.currentUser)
328
- return reject(new Error('Not logged in — run: login'));
329
-
330
- const timeout = setTimeout(() => {
331
- this.socket.off('thinknsh:joined', onJoined);
332
- this.socket.off('thinknsh:error', onError);
333
- this.socket.off('thinknsh:ready', onReady);
334
- reject(new Error('Join timeout'));
335
- }, 8000);
336
-
337
- const onJoined = (data) => {
338
- clearTimeout(timeout);
339
- this.socket.off('thinknsh:error', onError);
340
- this.roomId = data.roomId;
341
- resolve(data);
342
- };
343
-
344
- // onReady declared at top so onError can reference it regardless of branch
345
- let onReady = null;
346
-
347
- const onError = (data) => {
348
- clearTimeout(timeout);
349
- this.socket.off('thinknsh:joined', onJoined);
350
- if (onReady) this.socket.off('thinknsh:ready', onReady);
351
- reject(new Error(data.message || 'Join error'));
352
- };
353
-
354
- const doJoin = () => {
355
- this.socket.once('thinknsh:joined', onJoined);
356
- this.socket.once('thinknsh:error', onError);
357
- this._emitJoin(roomId);
358
- };
359
-
360
- if (this._serverReady) {
361
- doJoin();
362
- } else {
363
- onReady = () => {
364
- this._serverReady = true;
365
- doJoin();
366
- };
367
- this.socket.once('thinknsh:ready', onReady);
368
- }
369
- });
370
- }
371
-
372
- _emitJoin(roomId) {
373
- if (!this.socket || !this.currentUser) return;
374
- this.socket.emit('thinknsh:join', {
375
- roomId,
376
- userId: this.currentUser.userId,
377
- name: this.currentUser.name,
378
- userType: this.currentUser.userType,
379
- });
380
- }
381
-
382
- // ─── Leave room ───────────────────────────────────────────────────────────
383
-
384
- leaveRoom() {
385
- if (this.socket && this.roomId) {
386
- this.socket.emit('leave-room', { roomId: this.roomId });
387
- this.roomId = null;
388
- }
389
- return Promise.resolve({ success: true });
390
- }
391
-
392
- sendMessage(message, replyTo = null) {
393
- return new Promise((resolve, reject) => {
394
- if (!this.socket?.connected) return reject(new Error('Not connected'));
395
-
396
- const roomId = this.roomId;
397
- if (!roomId) return reject(new Error('Not in any room. Use: join <room-id>'));
398
-
399
- if (!this.currentUser) return reject(new Error('Not authenticated. Run: login'));
400
-
401
- this.socket.emit('thinknsh:message', {
402
- roomId,
403
- userId: this.currentUser.userId,
404
- name: this.currentUser.name,
405
- userType: this.currentUser.userType || 'User',
406
- message,
407
- replyTo: replyTo || null
408
- });
409
-
410
- resolve();
411
- });
412
- }
413
- // ─── History ──────────────────────────────────────────────────────────────
414
-
415
- getHistory(limit = 30, skip = 0) {
416
- if (!this.connected || !this.socket || !this.roomId) return;
417
- this.socket.emit('thinknsh:getHistory', { roomId: this.roomId, limit, skip });
418
- }
419
-
420
- // ─── Notifications ────────────────────────────────────────────────────────
421
-
422
- getNotifications() {
423
- if (!this.connected || !this.currentUser) return;
424
- this.socket.emit('thinknsh:getNotifications', { userId: this.currentUser.userId });
425
- }
426
-
427
- sendNotification(notification, target = 'room') {
428
- if (!this.connected || !this.socket)
429
- return Promise.reject(new Error('Not connected'));
430
-
431
- this.socket.emit('notification:send', {
432
- type: notification.type || 'info',
433
- title: notification.title || 'Notification',
434
- message: notification.message,
435
- data: notification.data || {},
436
- target: target === 'room' ? this.roomId : target,
437
- timestamp: new Date().toISOString()
438
- });
439
-
440
- return Promise.resolve({ success: true });
441
- }
442
-
443
- markNotificationsRead(notificationIds = []) {
444
- if (!this.connected || !this.currentUser) return;
445
- this.socket.emit('thinknsh:markRead', {
446
- userId: this.currentUser.userId,
447
- notificationIds,
448
- });
449
- }
450
-
451
- // ─── Helpers ──────────────────────────────────────────────────────────────
452
-
453
- processPendingMessages() {
454
- if (this.messageQueue.length && this.roomId && this.currentUser) {
455
- this.messageQueue.forEach(msg => this.sendMessage(msg));
456
- this.messageQueue = [];
457
- }
458
- }
459
-
460
- disconnect() {
461
- if (this.socket) {
462
- this.socket.removeAllListeners();
463
- this.socket.disconnect();
464
- this.socket = null;
465
- this.connected = false;
466
- this.currentUser = null;
467
- this.roomId = null;
468
- this._handlersSetup = false;
469
- this._serverReady = false;
470
- this._serverReady = false;
471
- }
472
- }
473
-
474
- isConnected() { return this.connected && !!this.socket?.connected; }
475
- getCurrentRoom() { return this.roomId; }
476
- getSocketId() { return this.socket?.id || null; }
477
- getParticipants(){ return Promise.resolve([]); }
478
- sendTyping() {}
479
- ping() { if (this.connected && this.socket) this.socket.emit('ping'); }
480
- }
481
-
482
- module.exports = WebSocketManager;
@@ -1,184 +0,0 @@
1
- /*
2
- Notification System for ThinkNCollab Shell
3
- */
4
-
5
- const EventEmitter = require('events');
6
- const chalk = require('chalk');
7
-
8
- class NotificationManager extends EventEmitter {
9
- constructor(options = {}) {
10
- super();
11
-
12
- this.options = {
13
- sound: options.sound || false,
14
- desktop: options.desktop || false,
15
- ...options
16
- };
17
-
18
- this.notifications = [];
19
- this.maxNotifications = 100;
20
- this.unreadCount = 0;
21
- }
22
-
23
- /* Show a notification
24
- */
25
- show(notification) {
26
- const notif = {
27
- id: `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
28
- type: notification.type || 'info',
29
- title: notification.title || 'Notification',
30
- message: notification.message,
31
- timestamp: new Date(),
32
- read: false,
33
- data: notification.data || {}
34
- };
35
-
36
- this.notifications.unshift(notif);
37
- this.unreadCount++;
38
-
39
- // Trim old notifications
40
- if (this.notifications.length > this.maxNotifications) {
41
- this.notifications.pop();
42
- }
43
-
44
- // Emit event
45
- this.emit('notification', notif);
46
-
47
- // Show in console
48
- this.showInConsole(notif);
49
-
50
- // Desktop notification
51
- if (this.options.desktop) {
52
- this.showDesktopNotification(notif);
53
- }
54
-
55
- // Sound
56
- if (this.options.sound) {
57
- this.playSound();
58
- }
59
-
60
- return notif;
61
- }
62
-
63
- /* Show notification in console
64
- */
65
- showInConsole(notification) {
66
- let color = chalk.blue;
67
- let symbol = 'ℹ';
68
-
69
- switch (notification.type) {
70
- case 'success':
71
- color = chalk.green;
72
- symbol = '✓';
73
- break;
74
- case 'warning':
75
- color = chalk.yellow;
76
- symbol = '⚠';
77
- break;
78
- case 'error':
79
- color = chalk.red;
80
- symbol = '✗';
81
- break;
82
- case 'info':
83
- default:
84
- color = chalk.blue;
85
- symbol = 'ℹ';
86
- break;
87
- }
88
-
89
- console.log(color(`${symbol} ${notification.title}: ${notification.message}`));
90
- }
91
-
92
- /* Show desktop notification (if supported)
93
- */
94
- showDesktopNotification(notification) {
95
- // Check if running in Electron
96
- if (process.type === 'renderer' || process.type === 'browser') {
97
- try {
98
- const { Notification } = require('electron');
99
- new Notification({
100
- title: notification.title,
101
- body: notification.message,
102
- silent: !this.options.sound
103
- }).show();
104
- } catch (error) {
105
- // Electron not available
106
- }
107
- }
108
- }
109
-
110
- /* Play notification sound
111
- */
112
- playSound() {
113
- process.stdout.write('\x07'); // terminal bell — zero dependencies
114
- }
115
-
116
- /* Mark notification as read
117
- */
118
- markAsRead(id) {
119
- const notif = this.notifications.find(n => n.id === id);
120
- if (notif && !notif.read) {
121
- notif.read = true;
122
- this.unreadCount--;
123
- this.emit('read', notif);
124
- }
125
- }
126
-
127
- /* Mark all as read
128
- */
129
- markAllAsRead() {
130
- this.notifications.forEach(n => {
131
- if (!n.read) {
132
- n.read = true;
133
- }
134
- });
135
- this.unreadCount = 0;
136
- this.emit('allRead');
137
- }
138
-
139
- /* Get unread notifications
140
- */
141
- getUnread() {
142
- return this.notifications.filter(n => !n.read);
143
- }
144
-
145
- /* Get all notifications
146
- */
147
- getAll() {
148
- return this.notifications;
149
- }
150
-
151
- /* Clear all notifications
152
- */
153
- clear() {
154
- this.notifications = [];
155
- this.unreadCount = 0;
156
- this.emit('cleared');
157
- }
158
-
159
- /* Create success notification
160
- */
161
- success(title, message, data = {}) {
162
- return this.show({ type: 'success', title, message, data });
163
- }
164
-
165
- /* Create error notification
166
- */
167
- error(title, message, data = {}) {
168
- return this.show({ type: 'error', title, message, data });
169
- }
170
-
171
- /* Create warning notification
172
- */
173
- warning(title, message, data = {}) {
174
- return this.show({ type: 'warning', title, message, data });
175
- }
176
-
177
- /* Create info notification
178
- */
179
- info(title, message, data = {}) {
180
- return this.show({ type: 'info', title, message, data });
181
- }
182
- }
183
-
184
- module.exports = NotificationManager;