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
package/src/core/api-client.js
CHANGED
|
@@ -1,246 +1,246 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* src/core/api-client.js
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const https = require('https');
|
|
6
|
-
const http = require('http');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const fs = require('fs-extra');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
|
|
11
|
-
class ThinkNCollabAPI {
|
|
12
|
-
|
|
13
|
-
constructor(options = {}) {
|
|
14
|
-
this.apiUrl = (options.apiUrl || 'https://thinkncollab.com').replace(/\/$/, '');
|
|
15
|
-
this.wsUrl = (options.wsUrl || 'https://thinkncollab.com').replace(/\/$/, '');
|
|
16
|
-
this.session = null;
|
|
17
|
-
this.sessionPath = path.join(os.homedir(), '.thinkncollab', 'session.json');
|
|
18
|
-
this._loadSession();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ─── Session ──────────────────────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
_loadSession() {
|
|
24
|
-
try {
|
|
25
|
-
if (fs.existsSync(this.sessionPath))
|
|
26
|
-
this.session = fs.readJsonSync(this.sessionPath);
|
|
27
|
-
} catch {}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
_saveSession(session) {
|
|
31
|
-
try {
|
|
32
|
-
fs.ensureDirSync(path.dirname(this.sessionPath));
|
|
33
|
-
fs.writeJsonSync(this.sessionPath, session);
|
|
34
|
-
this.session = session;
|
|
35
|
-
} catch {}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
_clearSession() {
|
|
39
|
-
try { fs.removeSync(this.sessionPath); } catch {}
|
|
40
|
-
this.session = null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
isAuthenticated() { return !!(this.session?.token); }
|
|
44
|
-
getToken() { return this.session?.token || null; }
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
getUser() {
|
|
53
|
-
const u = this.session?.user;
|
|
54
|
-
if (!u) return null;
|
|
55
|
-
const id = (u._id || u.userId || '').toString();
|
|
56
|
-
return {
|
|
57
|
-
_id: id,
|
|
58
|
-
userId: id, // ws.setUser / _emitJoin use userId
|
|
59
|
-
email: u.email || '',
|
|
60
|
-
name: u.name || '',
|
|
61
|
-
userType: u.userType || u.model || 'User',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ─── HTTP helper ──────────────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
_request(method, endpoint, body = null, headers = {}) {
|
|
68
|
-
return new Promise((resolve, reject) => {
|
|
69
|
-
const url = new URL(this.apiUrl + endpoint);
|
|
70
|
-
const isHttps = url.protocol === 'https:';
|
|
71
|
-
const lib = isHttps ? https : http;
|
|
72
|
-
const bodyStr = body ? JSON.stringify(body) : null;
|
|
73
|
-
|
|
74
|
-
const options = {
|
|
75
|
-
hostname: url.hostname,
|
|
76
|
-
port: url.port || (isHttps ? 443 : 80),
|
|
77
|
-
path: url.pathname + url.search,
|
|
78
|
-
method,
|
|
79
|
-
headers: {
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
'Accept': 'application/json',
|
|
82
|
-
...(this.session?.token ? { 'x-shell-token': this.session.token } : {}),
|
|
83
|
-
...headers,
|
|
84
|
-
...(bodyStr ? { 'Content-Length': Buffer.byteLength(bodyStr) } : {})
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const req = lib.request(options, (res) => {
|
|
89
|
-
let data = '';
|
|
90
|
-
res.on('data', chunk => data += chunk);
|
|
91
|
-
res.on('end', () => {
|
|
92
|
-
try {
|
|
93
|
-
const parsed = JSON.parse(data);
|
|
94
|
-
if (res.statusCode >= 400)
|
|
95
|
-
reject(new Error(parsed.error || parsed.message || `HTTP ${res.statusCode}`));
|
|
96
|
-
else
|
|
97
|
-
resolve(parsed);
|
|
98
|
-
} catch {
|
|
99
|
-
reject(new Error(`Invalid response: ${data.slice(0, 100)}`));
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
req.on('error', (err) => {
|
|
105
|
-
if (err.code === 'ECONNREFUSED')
|
|
106
|
-
reject(new Error(`Cannot connect to server at ${this.apiUrl} — is it running?`));
|
|
107
|
-
else
|
|
108
|
-
reject(err);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (bodyStr) req.write(bodyStr);
|
|
112
|
-
req.end();
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ─── Auth ─────────────────────────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
async login(email, password) {
|
|
119
|
-
const result = await this._request('POST', '/thinknsh/login', null, {
|
|
120
|
-
'x-email': email,
|
|
121
|
-
'x-password': password,
|
|
122
|
-
'x-machine-id': os.hostname()
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
this._saveSession({
|
|
126
|
-
token: result.shellToken,
|
|
127
|
-
shellSessionId: result.shellSessionId,
|
|
128
|
-
user: {
|
|
129
|
-
_id: result._id,
|
|
130
|
-
userId: result._id,
|
|
131
|
-
email: result.email,
|
|
132
|
-
name: result.name,
|
|
133
|
-
userType: result.userType || result.model || 'User',
|
|
134
|
-
},
|
|
135
|
-
timestamp: new Date().toISOString()
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
token: result.shellToken,
|
|
140
|
-
user: this.getUser()
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ─── Session event logger ─────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
async logEvent(type, roomId = null, meta = {}) {
|
|
147
|
-
const sessionId = this.session?.shellSessionId;
|
|
148
|
-
if (!sessionId) return;
|
|
149
|
-
await this._request('POST', '/thinknsh/session/event', {
|
|
150
|
-
sessionId, type, roomId, meta
|
|
151
|
-
}).catch(() => {}); // silently fail — never block shell
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ─── Heartbeat — keeps lastSeenAt fresh while shell is open ──────────────
|
|
155
|
-
|
|
156
|
-
startHeartbeat(intervalMs = 60000) {
|
|
157
|
-
if (this._heartbeatTimer) return; // already running
|
|
158
|
-
this._heartbeatTimer = setInterval(() => {
|
|
159
|
-
const sessionId = this.session?.shellSessionId;
|
|
160
|
-
if (!sessionId) return;
|
|
161
|
-
this._request('POST', '/thinknsh/session/event', {
|
|
162
|
-
sessionId,
|
|
163
|
-
type: 'heartbeat',
|
|
164
|
-
meta: { ts: new Date().toISOString() }
|
|
165
|
-
}).catch(() => {});
|
|
166
|
-
}, intervalMs);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
stopHeartbeat() {
|
|
170
|
-
if (this._heartbeatTimer) {
|
|
171
|
-
clearInterval(this._heartbeatTimer);
|
|
172
|
-
this._heartbeatTimer = null;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async logout() {
|
|
177
|
-
const sessionId = this.session?.shellSessionId;
|
|
178
|
-
if (sessionId) {
|
|
179
|
-
await this._request('POST', '/thinknsh/session/end', { sessionId }).catch(() => {});
|
|
180
|
-
}
|
|
181
|
-
this.stopHeartbeat();
|
|
182
|
-
this._clearSession();
|
|
183
|
-
return { success: true };
|
|
184
|
-
}
|
|
185
|
-
async whoami() {
|
|
186
|
-
if (!this.isAuthenticated()) throw new Error('Not logged in');
|
|
187
|
-
return this.getUser();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ─── Rooms ────────────────────────────────────────────────────────────────
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* joinRoom — validates membership server-side and returns room meta.
|
|
194
|
-
* Called by commands/room/join.js BEFORE the WebSocket thinknsh:join emit.
|
|
195
|
-
*/
|
|
196
|
-
async joinRoom(roomId, password) {
|
|
197
|
-
if (!roomId) throw new Error('roomId is required');
|
|
198
|
-
|
|
199
|
-
const result = await this._request('POST', `/thinknsh/rooms/${roomId}/join`,
|
|
200
|
-
password ? { password } : null
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
// Normalise response — server may return different shapes
|
|
204
|
-
return {
|
|
205
|
-
room: result.room || result,
|
|
206
|
-
participants: result.participants || result.members || [],
|
|
207
|
-
recentMessages: result.recentMessages || result.history || [],
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* getRooms — list rooms the user belongs to
|
|
213
|
-
*/
|
|
214
|
-
async getRooms() {
|
|
215
|
-
return this._request('GET', '/thinknsh/rooms');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ─── Tasks ────────────────────────────────────────────────────────────────
|
|
219
|
-
|
|
220
|
-
async mytasks(roomId, limit) {
|
|
221
|
-
if (!roomId) throw new Error('roomId is required');
|
|
222
|
-
const user = this.getUser();
|
|
223
|
-
let ep = `/thinknsh/tasks?roomId=${encodeURIComponent(roomId)}`;
|
|
224
|
-
if (user?._id) ep += `&userId=${encodeURIComponent(user._id)}`;
|
|
225
|
-
if (limit) ep += `&n=${limit}`;
|
|
226
|
-
return this._request('GET', ep);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Generic request wrapper for task command modules that call
|
|
231
|
-
* shell.api.request(method, path, { params, body })
|
|
232
|
-
*/
|
|
233
|
-
async request(method, endpoint, options = {}) {
|
|
234
|
-
let ep = endpoint;
|
|
235
|
-
|
|
236
|
-
// Append query params
|
|
237
|
-
if (options.params && Object.keys(options.params).length) {
|
|
238
|
-
const qs = new URLSearchParams(options.params).toString();
|
|
239
|
-
ep += (ep.includes('?') ? '&' : '?') + qs;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return this._request(method.toUpperCase(), ep, options.body || null);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
1
|
+
/**
|
|
2
|
+
* src/core/api-client.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
class ThinkNCollabAPI {
|
|
12
|
+
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.apiUrl = (options.apiUrl || 'https://thinkncollab.com').replace(/\/$/, '');
|
|
15
|
+
this.wsUrl = (options.wsUrl || 'https://thinkncollab.com').replace(/\/$/, '');
|
|
16
|
+
this.session = null;
|
|
17
|
+
this.sessionPath = path.join(os.homedir(), '.thinkncollab', 'session.json');
|
|
18
|
+
this._loadSession();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─── Session ──────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
_loadSession() {
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(this.sessionPath))
|
|
26
|
+
this.session = fs.readJsonSync(this.sessionPath);
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_saveSession(session) {
|
|
31
|
+
try {
|
|
32
|
+
fs.ensureDirSync(path.dirname(this.sessionPath));
|
|
33
|
+
fs.writeJsonSync(this.sessionPath, session);
|
|
34
|
+
this.session = session;
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_clearSession() {
|
|
39
|
+
try { fs.removeSync(this.sessionPath); } catch {}
|
|
40
|
+
this.session = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
isAuthenticated() { return !!(this.session?.token); }
|
|
44
|
+
getToken() { return this.session?.token || null; }
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
Returns user in a CONSISTENT shape used everywhere:
|
|
48
|
+
{ _id, userId, email, name, userType }
|
|
49
|
+
|
|
50
|
+
Both _id and userId are the same string so callers can use either.
|
|
51
|
+
*/
|
|
52
|
+
getUser() {
|
|
53
|
+
const u = this.session?.user;
|
|
54
|
+
if (!u) return null;
|
|
55
|
+
const id = (u._id || u.userId || '').toString();
|
|
56
|
+
return {
|
|
57
|
+
_id: id,
|
|
58
|
+
userId: id, // ws.setUser / _emitJoin use userId
|
|
59
|
+
email: u.email || '',
|
|
60
|
+
name: u.name || '',
|
|
61
|
+
userType: u.userType || u.model || 'User',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── HTTP helper ──────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
_request(method, endpoint, body = null, headers = {}) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const url = new URL(this.apiUrl + endpoint);
|
|
70
|
+
const isHttps = url.protocol === 'https:';
|
|
71
|
+
const lib = isHttps ? https : http;
|
|
72
|
+
const bodyStr = body ? JSON.stringify(body) : null;
|
|
73
|
+
|
|
74
|
+
const options = {
|
|
75
|
+
hostname: url.hostname,
|
|
76
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
77
|
+
path: url.pathname + url.search,
|
|
78
|
+
method,
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'Accept': 'application/json',
|
|
82
|
+
...(this.session?.token ? { 'x-shell-token': this.session.token } : {}),
|
|
83
|
+
...headers,
|
|
84
|
+
...(bodyStr ? { 'Content-Length': Buffer.byteLength(bodyStr) } : {})
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const req = lib.request(options, (res) => {
|
|
89
|
+
let data = '';
|
|
90
|
+
res.on('data', chunk => data += chunk);
|
|
91
|
+
res.on('end', () => {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(data);
|
|
94
|
+
if (res.statusCode >= 400)
|
|
95
|
+
reject(new Error(parsed.error || parsed.message || `HTTP ${res.statusCode}`));
|
|
96
|
+
else
|
|
97
|
+
resolve(parsed);
|
|
98
|
+
} catch {
|
|
99
|
+
reject(new Error(`Invalid response: ${data.slice(0, 100)}`));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
req.on('error', (err) => {
|
|
105
|
+
if (err.code === 'ECONNREFUSED')
|
|
106
|
+
reject(new Error(`Cannot connect to server at ${this.apiUrl} — is it running?`));
|
|
107
|
+
else
|
|
108
|
+
reject(err);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (bodyStr) req.write(bodyStr);
|
|
112
|
+
req.end();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── Auth ─────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
async login(email, password) {
|
|
119
|
+
const result = await this._request('POST', '/thinknsh/login', null, {
|
|
120
|
+
'x-email': email,
|
|
121
|
+
'x-password': password,
|
|
122
|
+
'x-machine-id': os.hostname()
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this._saveSession({
|
|
126
|
+
token: result.shellToken,
|
|
127
|
+
shellSessionId: result.shellSessionId,
|
|
128
|
+
user: {
|
|
129
|
+
_id: result._id,
|
|
130
|
+
userId: result._id,
|
|
131
|
+
email: result.email,
|
|
132
|
+
name: result.name,
|
|
133
|
+
userType: result.userType || result.model || 'User',
|
|
134
|
+
},
|
|
135
|
+
timestamp: new Date().toISOString()
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
token: result.shellToken,
|
|
140
|
+
user: this.getUser()
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── Session event logger ─────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
async logEvent(type, roomId = null, meta = {}) {
|
|
147
|
+
const sessionId = this.session?.shellSessionId;
|
|
148
|
+
if (!sessionId) return;
|
|
149
|
+
await this._request('POST', '/thinknsh/session/event', {
|
|
150
|
+
sessionId, type, roomId, meta
|
|
151
|
+
}).catch(() => {}); // silently fail — never block shell
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Heartbeat — keeps lastSeenAt fresh while shell is open ──────────────
|
|
155
|
+
|
|
156
|
+
startHeartbeat(intervalMs = 60000) {
|
|
157
|
+
if (this._heartbeatTimer) return; // already running
|
|
158
|
+
this._heartbeatTimer = setInterval(() => {
|
|
159
|
+
const sessionId = this.session?.shellSessionId;
|
|
160
|
+
if (!sessionId) return;
|
|
161
|
+
this._request('POST', '/thinknsh/session/event', {
|
|
162
|
+
sessionId,
|
|
163
|
+
type: 'heartbeat',
|
|
164
|
+
meta: { ts: new Date().toISOString() }
|
|
165
|
+
}).catch(() => {});
|
|
166
|
+
}, intervalMs);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
stopHeartbeat() {
|
|
170
|
+
if (this._heartbeatTimer) {
|
|
171
|
+
clearInterval(this._heartbeatTimer);
|
|
172
|
+
this._heartbeatTimer = null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async logout() {
|
|
177
|
+
const sessionId = this.session?.shellSessionId;
|
|
178
|
+
if (sessionId) {
|
|
179
|
+
await this._request('POST', '/thinknsh/session/end', { sessionId }).catch(() => {});
|
|
180
|
+
}
|
|
181
|
+
this.stopHeartbeat();
|
|
182
|
+
this._clearSession();
|
|
183
|
+
return { success: true };
|
|
184
|
+
}
|
|
185
|
+
async whoami() {
|
|
186
|
+
if (!this.isAuthenticated()) throw new Error('Not logged in');
|
|
187
|
+
return this.getUser();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─── Rooms ────────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* joinRoom — validates membership server-side and returns room meta.
|
|
194
|
+
* Called by commands/room/join.js BEFORE the WebSocket thinknsh:join emit.
|
|
195
|
+
*/
|
|
196
|
+
async joinRoom(roomId, password) {
|
|
197
|
+
if (!roomId) throw new Error('roomId is required');
|
|
198
|
+
|
|
199
|
+
const result = await this._request('POST', `/thinknsh/rooms/${roomId}/join`,
|
|
200
|
+
password ? { password } : null
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Normalise response — server may return different shapes
|
|
204
|
+
return {
|
|
205
|
+
room: result.room || result,
|
|
206
|
+
participants: result.participants || result.members || [],
|
|
207
|
+
recentMessages: result.recentMessages || result.history || [],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* getRooms — list rooms the user belongs to
|
|
213
|
+
*/
|
|
214
|
+
async getRooms() {
|
|
215
|
+
return this._request('GET', '/thinknsh/rooms');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ─── Tasks ────────────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
async mytasks(roomId, limit) {
|
|
221
|
+
if (!roomId) throw new Error('roomId is required');
|
|
222
|
+
const user = this.getUser();
|
|
223
|
+
let ep = `/thinknsh/tasks?roomId=${encodeURIComponent(roomId)}`;
|
|
224
|
+
if (user?._id) ep += `&userId=${encodeURIComponent(user._id)}`;
|
|
225
|
+
if (limit) ep += `&n=${limit}`;
|
|
226
|
+
return this._request('GET', ep);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generic request wrapper for task command modules that call
|
|
231
|
+
* shell.api.request(method, path, { params, body })
|
|
232
|
+
*/
|
|
233
|
+
async request(method, endpoint, options = {}) {
|
|
234
|
+
let ep = endpoint;
|
|
235
|
+
|
|
236
|
+
// Append query params
|
|
237
|
+
if (options.params && Object.keys(options.params).length) {
|
|
238
|
+
const qs = new URLSearchParams(options.params).toString();
|
|
239
|
+
ep += (ep.includes('?') ? '&' : '?') + qs;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return this._request(method.toUpperCase(), ep, options.body || null);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
246
|
module.exports = ThinkNCollabAPI;
|