thinkncollab-shell 0.0.8 → 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/bin/thinknsh.js +9 -7
- package/package.json +1 -1
- package/src/commands/auth/login.js +76 -76
- package/src/commands/auth/logout.js +31 -31
- package/src/commands/auth/register.js +51 -51
- package/src/commands/auth/whoami.js +51 -51
- package/src/commands/index.js +129 -129
- package/src/commands/message/say.js +48 -42
- package/src/commands/room/join.js +141 -141
- package/src/commands/room/leave.js +32 -32
- package/src/commands/room/rooms.js +65 -65
- package/src/commands/shell/notify.js +87 -87
- package/src/commands/shell/status.js +72 -72
- package/src/commands/tasks/complete.js +46 -46
- package/src/commands/tasks/create-task.js +100 -100
- package/src/commands/tasks/task-status.js +60 -60
- package/src/commands/tasks/task.js +74 -74
- package/src/commands/tasks/tasks.js +182 -182
- package/src/commands/team/invite.js +45 -45
- package/src/commands/team/myteam.js +77 -77
- package/src/commands/team/teammates.js +51 -51
- package/src/commands/terminal/share.js +251 -251
- package/src/core/api-client.js +245 -245
- package/src/core/project-config.js +99 -99
- package/src/core/shell.js +165 -36
- package/src/core/websocket.js +16 -15
- package/src/events/notifications.js +15 -28
- package/src/utils/logger.js +10 -20
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs-extra');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
const os = require('os');
|
|
20
|
-
|
|
21
|
-
const CONFIG_FILE = 'tncproject';
|
|
22
|
-
const MAGIC = Buffer.from('TNCP');
|
|
23
|
-
const ROOM_ID_SIZE = 24;
|
|
24
|
-
const MIN_SIZE = 4 + 1 + ROOM_ID_SIZE + 12 + 16 + 2; // 59 bytes minimum
|
|
25
|
-
|
|
26
|
-
function findConfigFile(startDir = process.cwd()) {
|
|
27
|
-
let dir = path.resolve(startDir);
|
|
28
|
-
const root = path.parse(dir).root;
|
|
29
|
-
|
|
30
|
-
while (true) {
|
|
31
|
-
const candidate = path.join(dir, CONFIG_FILE);
|
|
32
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
33
|
-
if (dir === root || dir === os.homedir()) break;
|
|
34
|
-
dir = path.dirname(dir);
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/
|
|
45
|
-
function loadProjectConfig(startDir = process.cwd()) {
|
|
46
|
-
const filePath = findConfigFile(startDir);
|
|
47
|
-
|
|
48
|
-
if (!filePath) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
'No .tncproject file found in this directory or any parent.\n' +
|
|
51
|
-
' Download it from: ThinkNCollab → Room → Project Config'
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const buf = fs.readFileSync(filePath);
|
|
56
|
-
|
|
57
|
-
if (buf.length < MIN_SIZE) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
'Invalid .tncproject — file too small or corrupted.\n' +
|
|
60
|
-
' Re-download it from the dashboard.'
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!buf.slice(0, 4).equals(MAGIC)) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
'Not a ThinkNCollab project config file.\n' +
|
|
67
|
-
' Re-download from the dashboard.'
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const version = buf.readUInt8(4);
|
|
72
|
-
if (version !== 0x01) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Unsupported .tncproject version (${version}).\n` +
|
|
75
|
-
' Update thinkncollab-shell: npm update -g thinkncollab-shell'
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Read roomId from plaintext prefix — bytes 5..28
|
|
80
|
-
const roomId = buf.slice(5, 5 + ROOM_ID_SIZE).toString('ascii').trim();
|
|
81
|
-
|
|
82
|
-
if (!/^[a-f0-9]{24}$/i.test(roomId)) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
'Corrupted .tncproject — invalid roomId.\n' +
|
|
85
|
-
' Re-download it from the dashboard.'
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
filePath,
|
|
91
|
-
roomId,
|
|
92
|
-
fileBase64: buf.toString('base64'), // sent as-is to server
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function hasProjectConfig(startDir = process.cwd()) {
|
|
97
|
-
return !!findConfigFile(startDir);
|
|
98
|
-
}
|
|
99
|
-
|
|
1
|
+
/*
|
|
2
|
+
src/core/project-config.js
|
|
3
|
+
|
|
4
|
+
Shell-side reader for .tncproject binary files.
|
|
5
|
+
NO crypto code — shell only reads the plaintext roomId prefix,
|
|
6
|
+
then sends the raw file to the server for decryption.
|
|
7
|
+
|
|
8
|
+
File layout (written by server):
|
|
9
|
+
[4 bytes] "TNCP" magic
|
|
10
|
+
[1 byte] version 0x01
|
|
11
|
+
[24 bytes] roomId ASCII hex ← only readable part
|
|
12
|
+
[12 bytes] AES-GCM IV ← opaque
|
|
13
|
+
[16 bytes] AES-GCM auth tag ← opaque
|
|
14
|
+
[N bytes] encrypted payload ← opaque
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs-extra');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
|
|
21
|
+
const CONFIG_FILE = 'tncproject';
|
|
22
|
+
const MAGIC = Buffer.from('TNCP');
|
|
23
|
+
const ROOM_ID_SIZE = 24;
|
|
24
|
+
const MIN_SIZE = 4 + 1 + ROOM_ID_SIZE + 12 + 16 + 2; // 59 bytes minimum
|
|
25
|
+
|
|
26
|
+
function findConfigFile(startDir = process.cwd()) {
|
|
27
|
+
let dir = path.resolve(startDir);
|
|
28
|
+
const root = path.parse(dir).root;
|
|
29
|
+
|
|
30
|
+
while (true) {
|
|
31
|
+
const candidate = path.join(dir, CONFIG_FILE);
|
|
32
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
33
|
+
if (dir === root || dir === os.homedir()) break;
|
|
34
|
+
dir = path.dirname(dir);
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
Read and lightly validate a .tncproject file.
|
|
41
|
+
Returns { filePath, roomId, fileBase64 } — no decryption.
|
|
42
|
+
|
|
43
|
+
@throws if file is missing, wrong format, or roomId looks wrong http://localhost:3001
|
|
44
|
+
*/
|
|
45
|
+
function loadProjectConfig(startDir = process.cwd()) {
|
|
46
|
+
const filePath = findConfigFile(startDir);
|
|
47
|
+
|
|
48
|
+
if (!filePath) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'No .tncproject file found in this directory or any parent.\n' +
|
|
51
|
+
' Download it from: ThinkNCollab → Room → Project Config'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const buf = fs.readFileSync(filePath);
|
|
56
|
+
|
|
57
|
+
if (buf.length < MIN_SIZE) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'Invalid .tncproject — file too small or corrupted.\n' +
|
|
60
|
+
' Re-download it from the dashboard.'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!buf.slice(0, 4).equals(MAGIC)) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'Not a ThinkNCollab project config file.\n' +
|
|
67
|
+
' Re-download from the dashboard.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const version = buf.readUInt8(4);
|
|
72
|
+
if (version !== 0x01) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Unsupported .tncproject version (${version}).\n` +
|
|
75
|
+
' Update thinkncollab-shell: npm update -g thinkncollab-shell'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Read roomId from plaintext prefix — bytes 5..28
|
|
80
|
+
const roomId = buf.slice(5, 5 + ROOM_ID_SIZE).toString('ascii').trim();
|
|
81
|
+
|
|
82
|
+
if (!/^[a-f0-9]{24}$/i.test(roomId)) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
'Corrupted .tncproject — invalid roomId.\n' +
|
|
85
|
+
' Re-download it from the dashboard.'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
filePath,
|
|
91
|
+
roomId,
|
|
92
|
+
fileBase64: buf.toString('base64'), // sent as-is to server
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasProjectConfig(startDir = process.cwd()) {
|
|
97
|
+
return !!findConfigFile(startDir);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
100
|
module.exports = { loadProjectConfig, hasProjectConfig, CONFIG_FILE };
|
package/src/core/shell.js
CHANGED
|
@@ -62,7 +62,7 @@ class ThinkNCollabShell extends EventEmitter {
|
|
|
62
62
|
this.aliases = new Map();
|
|
63
63
|
this.variables = new Map();
|
|
64
64
|
|
|
65
|
-
//
|
|
65
|
+
// Restore persisted session on startup
|
|
66
66
|
// Handles both .tncproject boot AND manual login from a previous session
|
|
67
67
|
if (this.api.isAuthenticated()) {
|
|
68
68
|
this.ws.setUser(this.api.getUser(), this.api.getToken());
|
|
@@ -101,40 +101,166 @@ class ThinkNCollabShell extends EventEmitter {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// ─── Spawn notification window ────────────────────────────────────────────
|
|
104
|
+
spawnNotificationWindow() {
|
|
105
|
+
const notifyScript = path.join(__dirname, 'notification-window.js');
|
|
106
|
+
const port = this.notifyPort;
|
|
107
|
+
const env = { ...process.env, THINKNSH_NOTIFY_PORT: String(port) };
|
|
108
|
+
|
|
109
|
+
const isWSL = process.platform === 'linux' &&
|
|
110
|
+
(() => { try { return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft'); } catch { return false; } })();
|
|
111
|
+
|
|
112
|
+
const fallback = () => {
|
|
113
|
+
console.log(chalk.yellow(`\n ⚠️ Could not open notification window.`));
|
|
114
|
+
console.log(chalk.dim(` Open a new terminal and run:`));
|
|
115
|
+
console.log(chalk.cyan(` THINKNSH_NOTIFY_PORT=${port} node "${notifyScript}"\n`));
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// ── WSL ──────────────────────────────────────────────────────────────
|
|
120
|
+
if (isWSL) {
|
|
121
|
+
const winScript = notifyScript
|
|
122
|
+
.replace(/^\/mnt\/([a-z])/, (_, d) => `${d.toUpperCase()}:`)
|
|
123
|
+
.replace(/\//g, '\\');
|
|
124
|
+
|
|
125
|
+
const launchWinNode = (winExe) => {
|
|
126
|
+
const p = spawn(
|
|
127
|
+
`start cmd.exe /k ""${winExe}" "${winScript}""`,
|
|
128
|
+
[],
|
|
129
|
+
{ detached: true, stdio: 'ignore', env, shell: true }
|
|
130
|
+
);
|
|
131
|
+
p.on('error', fallback);
|
|
132
|
+
p.unref();
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// 1. Known paths — instant
|
|
136
|
+
const known = [
|
|
137
|
+
'/mnt/c/Program Files/nodejs/node.exe',
|
|
138
|
+
'/mnt/c/Program Files (x86)/nodejs/node.exe',
|
|
139
|
+
];
|
|
140
|
+
const found = known.find(p => { try { return fs.existsSync(p); } catch { return false; } });
|
|
141
|
+
|
|
142
|
+
if (found) {
|
|
143
|
+
const winExe = found
|
|
144
|
+
.replace(/^\/mnt\/([a-z])/, (_, d) => `${d.toUpperCase()}:`)
|
|
145
|
+
.replace(/\//g, '\\');
|
|
146
|
+
launchWinNode(winExe);
|
|
147
|
+
} else {
|
|
148
|
+
// 2. Async where node
|
|
149
|
+
const child = spawn('cmd.exe', ['/c', 'where', 'node'], {
|
|
150
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
151
|
+
});
|
|
152
|
+
let out = '';
|
|
153
|
+
child.stdout.on('data', d => out += d);
|
|
154
|
+
child.on('close', () => {
|
|
155
|
+
const winExe = out.trim().split('\n')[0]?.trim();
|
|
156
|
+
if (winExe?.endsWith('.exe')) {
|
|
157
|
+
launchWinNode(winExe);
|
|
158
|
+
} else {
|
|
159
|
+
// 3. Last resort
|
|
160
|
+
const p = spawn(
|
|
161
|
+
`start cmd.exe /k "wsl.exe -- bash -c 'THINKNSH_NOTIFY_PORT=${port} node \\"${notifyScript}\\"; read'"`,
|
|
162
|
+
[],
|
|
163
|
+
{ detached: true, stdio: 'ignore', shell: true }
|
|
164
|
+
);
|
|
165
|
+
p.on('error', fallback);
|
|
166
|
+
p.unref();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
104
170
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
171
|
+
// ── Windows native ────────────────────────────────────────────────────
|
|
172
|
+
} else if (process.platform === 'win32') {
|
|
173
|
+
const nodeExe = process.execPath;
|
|
174
|
+
const isVSCode = process.env.TERM_PROGRAM === 'vscode';
|
|
175
|
+
|
|
176
|
+
if (isVSCode) {
|
|
177
|
+
console.log(chalk.yellow('\n VS Code detected — open a new terminal tab and run:'));
|
|
178
|
+
console.log(chalk.cyan(` $env:THINKNSH_NOTIFY_PORT=${port}; node "${notifyScript}"\n`));
|
|
179
|
+
} else {
|
|
180
|
+
const launchCmd = () => {
|
|
181
|
+
// shell:true — Windows quoting issues handle ho jaate hain
|
|
182
|
+
const p = spawn(
|
|
183
|
+
`start cmd.exe /k ""${nodeExe}" "${notifyScript}""`,
|
|
184
|
+
[],
|
|
185
|
+
{ detached: true, stdio: 'ignore', env, shell: true }
|
|
186
|
+
);
|
|
187
|
+
p.on('error', fallback);
|
|
188
|
+
p.unref();
|
|
189
|
+
};
|
|
108
190
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
191
|
+
const wt = spawn('wt.exe', [
|
|
192
|
+
'cmd.exe', '/k', `"${nodeExe}" "${notifyScript}"`
|
|
193
|
+
], { detached: true, stdio: 'ignore', env });
|
|
194
|
+
wt.on('error', launchCmd);
|
|
195
|
+
wt.unref();
|
|
196
|
+
}
|
|
197
|
+
// ── macOS ─────────────────────────────────────────────────────────────
|
|
198
|
+
} else if (process.platform === 'darwin') {
|
|
199
|
+
const nodeExe = process.execPath;
|
|
200
|
+
const isVSCode = process.env.TERM_PROGRAM === 'vscode';
|
|
201
|
+
if (isVSCode) {
|
|
202
|
+
console.log(chalk.yellow(`\n VS Code detected — open a new terminal tab and run:`));
|
|
203
|
+
console.log(chalk.cyan(` THINKNSH_NOTIFY_PORT=${port} node "${notifyScript}"\n`));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const terminalScript = `
|
|
207
|
+
tell application "Terminal"
|
|
208
|
+
do script "THINKNSH_NOTIFY_PORT=${port} '${nodeExe}' '${notifyScript}'"
|
|
209
|
+
activate
|
|
210
|
+
end tell
|
|
211
|
+
`;
|
|
212
|
+
try {
|
|
213
|
+
spawn('osascript', ['-e', terminalScript], {
|
|
214
|
+
detached: true, stdio: 'ignore'
|
|
215
|
+
}).unref();
|
|
216
|
+
} catch {
|
|
217
|
+
// iTerm2 fallback
|
|
218
|
+
const itermScript = `
|
|
219
|
+
tell application "iTerm2"
|
|
220
|
+
tell current window
|
|
221
|
+
create tab with default profile
|
|
222
|
+
tell current session of current tab
|
|
223
|
+
write text "THINKNSH_NOTIFY_PORT=${port} '${nodeExe}' '${notifyScript}'"
|
|
224
|
+
end tell
|
|
225
|
+
end tell
|
|
226
|
+
end tell
|
|
227
|
+
`;
|
|
228
|
+
try {
|
|
229
|
+
spawn('osascript', ['-e', itermScript], {
|
|
230
|
+
detached: true, stdio: 'ignore'
|
|
231
|
+
}).unref();
|
|
232
|
+
} catch { fallback(); }
|
|
132
233
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
234
|
+
|
|
235
|
+
// ── Linux native ──────────────────────────────────────────────────────
|
|
236
|
+
} else {
|
|
237
|
+
const nodeExe = process.execPath;
|
|
238
|
+
const bashCmd = `THINKNSH_NOTIFY_PORT=${port} '${nodeExe}' '${notifyScript}'; read`;
|
|
239
|
+
const terminals = [
|
|
240
|
+
['gnome-terminal', ['--', 'bash', '-c', bashCmd]],
|
|
241
|
+
['konsole', ['--noclose', '-e', 'bash', '-c', bashCmd]],
|
|
242
|
+
['xfce4-terminal', ['--hold', '-e', `bash -c "${bashCmd}"`]],
|
|
243
|
+
['tilix', ['-e', `bash -c "${bashCmd}"`]],
|
|
244
|
+
['alacritty', ['-e', 'bash', '-c', bashCmd]],
|
|
245
|
+
['kitty', ['bash', '-c', bashCmd]],
|
|
246
|
+
['xterm', ['-hold', '-e', `bash -c "${bashCmd}"`]],
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
let spawned = false;
|
|
250
|
+
for (const [bin, args] of terminals) {
|
|
251
|
+
try {
|
|
252
|
+
spawn(bin, args, { detached: true, stdio: 'ignore', env }).unref();
|
|
253
|
+
spawned = true;
|
|
254
|
+
break;
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
if (!spawned) fallback();
|
|
136
258
|
}
|
|
259
|
+
|
|
260
|
+
} catch (err) {
|
|
261
|
+
fallback();
|
|
137
262
|
}
|
|
263
|
+
}
|
|
138
264
|
|
|
139
265
|
// ─── WebSocket → Shell + TCP bridge ──────────────────────────────────────
|
|
140
266
|
|
|
@@ -308,8 +434,6 @@ setupWebSocketHandlers() {
|
|
|
308
434
|
return true;
|
|
309
435
|
}
|
|
310
436
|
|
|
311
|
-
// ─── Execute ──────────────────────────────────────────────────────────────
|
|
312
|
-
// shell.js — execute() mein ye change karo
|
|
313
437
|
async execute(input) {
|
|
314
438
|
if (!input.trim()) return;
|
|
315
439
|
|
|
@@ -317,6 +441,12 @@ async execute(input) {
|
|
|
317
441
|
const cmdName = args[0].toLowerCase();
|
|
318
442
|
const cmdArgs = args.slice(1);
|
|
319
443
|
|
|
444
|
+
// Skip sensitive commands
|
|
445
|
+
const skipLog = ['login', 'logout', 'set', 'unset'];
|
|
446
|
+
if (!skipLog.includes(cmdName)) {
|
|
447
|
+
await this.api.logEvent('command', null, { cmd: cmdName });
|
|
448
|
+
}
|
|
449
|
+
|
|
320
450
|
if (this.commands.has(cmdName)) {
|
|
321
451
|
try { await this.commands.get(cmdName).handler(cmdArgs, this); }
|
|
322
452
|
catch (err) { console.log(chalk.red(`Error: ${err.message}`)); }
|
|
@@ -332,8 +462,7 @@ async execute(input) {
|
|
|
332
462
|
}
|
|
333
463
|
}
|
|
334
464
|
|
|
335
|
-
|
|
336
|
-
const blockedOnWindows = [ 'open', 'which', 'grep', 'touch', 'cat'];
|
|
465
|
+
const blockedOnWindows = ['open', 'which', 'grep', 'touch', 'cat'];
|
|
337
466
|
if (process.platform === 'win32' && blockedOnWindows.includes(cmdName)) {
|
|
338
467
|
console.log(chalk.red(`'${cmdName}' is not a thinknsh command.`));
|
|
339
468
|
console.log(chalk.dim(` Type 'help' to see available commands.`));
|
|
@@ -523,7 +652,7 @@ async execute(input) {
|
|
|
523
652
|
stdio: 'inherit'
|
|
524
653
|
});
|
|
525
654
|
child.on('close', (code) => {
|
|
526
|
-
// Update currentDir in case command changed it (cd won't but others might)
|
|
655
|
+
// Update currentDir in case command changed it (cd won't but others might) http://localhost:3001
|
|
527
656
|
try { this.currentDir = process.cwd(); } catch {}
|
|
528
657
|
process.stdout.write('\r\n');
|
|
529
658
|
resolve();
|
|
@@ -539,7 +668,7 @@ async execute(input) {
|
|
|
539
668
|
try { const figlet = require('figlet'); console.log(chalk.cyan(figlet.textSync('ThinkNCollab', { font: 'Standard' }))); } catch {}
|
|
540
669
|
console.log(chalk.cyan(`
|
|
541
670
|
╔══════════════════════════════════════════════════════╗
|
|
542
|
-
║ ThinkNCollab Shell v0.0.
|
|
671
|
+
║ ThinkNCollab Shell v0.0.10 ║
|
|
543
672
|
║ Type 'help' for commands ║
|
|
544
673
|
╚══════════════════════════════════════════════════════╝`));
|
|
545
674
|
}
|
package/src/core/websocket.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/*
|
|
2
|
+
src/core/websocket.js — WebSocket Manager for ThinkNCollab Shell
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { io } = require('socket.io-client');
|
|
@@ -113,6 +113,7 @@ class WebSocketManager extends EventEmitter {
|
|
|
113
113
|
* { userId, name, userType } ← legacy
|
|
114
114
|
*/
|
|
115
115
|
setUser(user, token = null) {
|
|
116
|
+
this._user = user;
|
|
116
117
|
if (!user) return;
|
|
117
118
|
|
|
118
119
|
if (token) this._authToken = token;
|
|
@@ -295,27 +296,27 @@ class WebSocketManager extends EventEmitter {
|
|
|
295
296
|
return Promise.resolve({ success: true });
|
|
296
297
|
}
|
|
297
298
|
|
|
298
|
-
|
|
299
|
+
sendMessage(message, replyTo = null) {
|
|
300
|
+
return new Promise((resolve, reject) => {
|
|
301
|
+
if (!this.socket?.connected) return reject(new Error('Not connected'));
|
|
299
302
|
|
|
300
|
-
|
|
301
|
-
if (!
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
if (!this.roomId) return Promise.reject(new Error('Not in a room — run: join <room-id>'));
|
|
306
|
-
if (!this.currentUser) return Promise.reject(new Error('Not logged in — run: login'));
|
|
303
|
+
const roomId = this.roomId;
|
|
304
|
+
if (!roomId) return reject(new Error('Not in any room. Use: join <room-id>'));
|
|
305
|
+
|
|
306
|
+
if (!this.currentUser) return reject(new Error('Not authenticated. Run: login'));
|
|
307
307
|
|
|
308
308
|
this.socket.emit('thinknsh:message', {
|
|
309
|
-
roomId
|
|
309
|
+
roomId,
|
|
310
310
|
userId: this.currentUser.userId,
|
|
311
311
|
name: this.currentUser.name,
|
|
312
|
-
userType: this.currentUser.userType,
|
|
312
|
+
userType: this.currentUser.userType || 'User',
|
|
313
313
|
message,
|
|
314
|
+
replyTo: replyTo || null
|
|
314
315
|
});
|
|
315
316
|
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
317
|
+
resolve();
|
|
318
|
+
});
|
|
319
|
+
}
|
|
319
320
|
// ─── History ──────────────────────────────────────────────────────────────
|
|
320
321
|
|
|
321
322
|
getHistory(limit = 30, skip = 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/*
|
|
2
|
+
Notification System for ThinkNCollab Shell
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const EventEmitter = require('events');
|
|
@@ -20,8 +20,7 @@ class NotificationManager extends EventEmitter {
|
|
|
20
20
|
this.unreadCount = 0;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
* Show a notification
|
|
23
|
+
/* Show a notification
|
|
25
24
|
*/
|
|
26
25
|
show(notification) {
|
|
27
26
|
const notif = {
|
|
@@ -61,8 +60,7 @@ class NotificationManager extends EventEmitter {
|
|
|
61
60
|
return notif;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
* Show notification in console
|
|
63
|
+
/* Show notification in console
|
|
66
64
|
*/
|
|
67
65
|
showInConsole(notification) {
|
|
68
66
|
let color = chalk.blue;
|
|
@@ -91,8 +89,7 @@ class NotificationManager extends EventEmitter {
|
|
|
91
89
|
console.log(color(`${symbol} ${notification.title}: ${notification.message}`));
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
|
|
95
|
-
* Show desktop notification (if supported)
|
|
92
|
+
/* Show desktop notification (if supported)
|
|
96
93
|
*/
|
|
97
94
|
showDesktopNotification(notification) {
|
|
98
95
|
// Check if running in Electron
|
|
@@ -110,16 +107,14 @@ class NotificationManager extends EventEmitter {
|
|
|
110
107
|
}
|
|
111
108
|
}
|
|
112
109
|
|
|
113
|
-
|
|
114
|
-
* Play notification sound
|
|
110
|
+
/* Play notification sound
|
|
115
111
|
*/
|
|
116
112
|
playSound() {
|
|
117
113
|
// TODO: Implement sound playing
|
|
118
114
|
// This would use 'sound-play' or similar package
|
|
119
115
|
}
|
|
120
116
|
|
|
121
|
-
|
|
122
|
-
* Mark notification as read
|
|
117
|
+
/* Mark notification as read
|
|
123
118
|
*/
|
|
124
119
|
markAsRead(id) {
|
|
125
120
|
const notif = this.notifications.find(n => n.id === id);
|
|
@@ -130,8 +125,7 @@ class NotificationManager extends EventEmitter {
|
|
|
130
125
|
}
|
|
131
126
|
}
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
* Mark all as read
|
|
128
|
+
/* Mark all as read
|
|
135
129
|
*/
|
|
136
130
|
markAllAsRead() {
|
|
137
131
|
this.notifications.forEach(n => {
|
|
@@ -143,22 +137,19 @@ class NotificationManager extends EventEmitter {
|
|
|
143
137
|
this.emit('allRead');
|
|
144
138
|
}
|
|
145
139
|
|
|
146
|
-
|
|
147
|
-
* Get unread notifications
|
|
140
|
+
/* Get unread notifications
|
|
148
141
|
*/
|
|
149
142
|
getUnread() {
|
|
150
143
|
return this.notifications.filter(n => !n.read);
|
|
151
144
|
}
|
|
152
145
|
|
|
153
|
-
|
|
154
|
-
* Get all notifications
|
|
146
|
+
/* Get all notifications
|
|
155
147
|
*/
|
|
156
148
|
getAll() {
|
|
157
149
|
return this.notifications;
|
|
158
150
|
}
|
|
159
151
|
|
|
160
|
-
|
|
161
|
-
* Clear all notifications
|
|
152
|
+
/* Clear all notifications
|
|
162
153
|
*/
|
|
163
154
|
clear() {
|
|
164
155
|
this.notifications = [];
|
|
@@ -166,29 +157,25 @@ class NotificationManager extends EventEmitter {
|
|
|
166
157
|
this.emit('cleared');
|
|
167
158
|
}
|
|
168
159
|
|
|
169
|
-
|
|
170
|
-
* Create success notification
|
|
160
|
+
/* Create success notification
|
|
171
161
|
*/
|
|
172
162
|
success(title, message, data = {}) {
|
|
173
163
|
return this.show({ type: 'success', title, message, data });
|
|
174
164
|
}
|
|
175
165
|
|
|
176
|
-
|
|
177
|
-
* Create error notification
|
|
166
|
+
/* Create error notification
|
|
178
167
|
*/
|
|
179
168
|
error(title, message, data = {}) {
|
|
180
169
|
return this.show({ type: 'error', title, message, data });
|
|
181
170
|
}
|
|
182
171
|
|
|
183
|
-
|
|
184
|
-
* Create warning notification
|
|
172
|
+
/* Create warning notification
|
|
185
173
|
*/
|
|
186
174
|
warning(title, message, data = {}) {
|
|
187
175
|
return this.show({ type: 'warning', title, message, data });
|
|
188
176
|
}
|
|
189
177
|
|
|
190
|
-
|
|
191
|
-
* Create info notification
|
|
178
|
+
/* Create info notification
|
|
192
179
|
*/
|
|
193
180
|
info(title, message, data = {}) {
|
|
194
181
|
return this.show({ type: 'info', title, message, data });
|