thinkncollab-shell 0.0.6 โ 0.0.8
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 +322 -179
- package/bin/thinknsh.js +1 -1
- package/package.json +1 -1
- package/src/commands/auth/whoami.js +44 -39
- package/src/commands/index.js +7 -0
- package/src/commands/tasks/complete.js +1 -1
- package/src/commands/tasks/task-status.js +61 -0
- package/src/commands/tasks/tasks.js +165 -56
- package/src/core/api-client.js +62 -26
- package/src/core/project-config.js +2 -2
- package/src/core/shell.js +67 -48
package/bin/thinknsh.js
CHANGED
|
@@ -46,7 +46,7 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const shell = new ThinkNCollabShell({
|
|
49
|
-
serverUrl: process.env.THINKNCOLLAB_SERVER || 'https://thinkncollab.com',
|
|
49
|
+
serverUrl: process.env.THINKNCOLLAB_SERVER || 'https://thinkncollab.com',
|
|
50
50
|
apiUrl: process.env.THINKNCOLLAB_API_URL || 'https://thinkncollab.com',
|
|
51
51
|
wsUrl: process.env.THINKNCOLLAB_WS_URL || 'https://thinkncollab.com',
|
|
52
52
|
autoConnect: process.env.THINKNCOLLAB_AUTO_CONNECT === 'true'
|
package/package.json
CHANGED
|
@@ -1,47 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Whoami Command
|
|
3
|
-
*
|
|
4
|
-
* Usage: whoami
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
const chalk = require('chalk');
|
|
8
2
|
|
|
9
3
|
module.exports = {
|
|
10
4
|
name: 'whoami',
|
|
11
5
|
description: 'Show current user information',
|
|
12
6
|
aliases: ['user', 'me'],
|
|
13
|
-
requiresAuth:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
7
|
+
requiresAuth: false,
|
|
8
|
+
|
|
9
|
+
async execute(args, shell) {
|
|
10
|
+
const user = shell.api.getUser();
|
|
11
|
+
const room = shell.ws.getCurrentRoom?.();
|
|
12
|
+
const session = shell.api.session;
|
|
13
|
+
|
|
14
|
+
// โ
Server se last session history fetch karo
|
|
15
|
+
let lastSession = null;
|
|
16
|
+
try {
|
|
17
|
+
const history = await shell.api._request('GET', '/thinknsh/session/history');
|
|
18
|
+
// Current session chhod ke last wali lo
|
|
19
|
+
const prev = history.find(s => s._id !== session?.shellSessionId);
|
|
20
|
+
if (prev) lastSession = prev;
|
|
21
|
+
} catch {}
|
|
22
|
+
|
|
23
|
+
console.log(chalk.cyan('\n๐ค User Information:'));
|
|
24
|
+
console.log(chalk.dim('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
25
|
+
console.log(` ${chalk.yellow('Name:')} ${user.name || 'Not set'}`);
|
|
26
|
+
console.log(` ${chalk.yellow('Email:')} ${user.email}`);
|
|
27
|
+
console.log(` ${chalk.yellow('User ID:')} ${user._id || 'N/A'}`);
|
|
28
|
+
console.log(` ${chalk.yellow('Type:')} ${user.userType || 'User'}`);
|
|
29
|
+
console.log(` ${chalk.yellow('Status:')} ${shell.ws.isConnected?.() ? chalk.green('โ Online') : chalk.gray('โ Offline')}`);
|
|
30
|
+
|
|
31
|
+
if (room) {
|
|
32
|
+
console.log(` ${chalk.yellow('Room:')} ${chalk.cyan(room)}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (session?.timestamp) {
|
|
36
|
+
console.log(` ${chalk.yellow('Login At:')} ${chalk.dim(new Date(session.timestamp).toLocaleString())}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// โ
Server se actual logout time
|
|
40
|
+
if (lastSession?.logoutAt) {
|
|
41
|
+
console.log(` ${chalk.yellow('Last Logout:')} ${chalk.dim(new Date(lastSession.logoutAt).toLocaleString())}`);
|
|
42
|
+
} else if (lastSession?.lastSeenAt) {
|
|
43
|
+
console.log(` ${chalk.yellow('Last Seen:')} ${chalk.dim(new Date(lastSession.lastSeenAt).toLocaleString())} ${chalk.red('(crash?)')}`);
|
|
46
44
|
}
|
|
45
|
+
|
|
46
|
+
if (lastSession?.machineId) {
|
|
47
|
+
console.log(` ${chalk.yellow('Last Machine:')} ${chalk.dim(lastSession.machineId)}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
47
52
|
};
|
package/src/commands/index.js
CHANGED
|
@@ -51,13 +51,20 @@ const load = (path, category) => {
|
|
|
51
51
|
// Tasks
|
|
52
52
|
load('./tasks/tasks', 'tasks');
|
|
53
53
|
load('./tasks/task', 'tasks');
|
|
54
|
+
load('./tasks/task-status', 'tasks');
|
|
54
55
|
// load('./tasks/start', 'tasks');
|
|
55
56
|
load('./tasks/complete', 'tasks');
|
|
56
57
|
// load('./tasks/comment', 'tasks');
|
|
57
58
|
load('./tasks/create-task', 'tasks');
|
|
59
|
+
|
|
58
60
|
|
|
59
61
|
// Terminal
|
|
60
62
|
load('./terminal/share', 'terminal');
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
register(command, category) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/commands/tasks/task-status.js
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
const STATUS_MAP = {
|
|
5
|
+
accept: 'accepted',
|
|
6
|
+
start: 'inprogress',
|
|
7
|
+
complete: 'completed',
|
|
8
|
+
reject: 'rejected',
|
|
9
|
+
done: 'completed',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
name: 'task-status',
|
|
14
|
+
description: 'Update your task status',
|
|
15
|
+
aliases: ['accept', 'start', 'complete', 'reject', 'done'],
|
|
16
|
+
requiresAuth: true,
|
|
17
|
+
requiresRoom: true,
|
|
18
|
+
|
|
19
|
+
async execute(args, shell) {
|
|
20
|
+
const roomId = shell.ws.getCurrentRoom();
|
|
21
|
+
const taskId = args[0];
|
|
22
|
+
// invoked as 'accept <id>', 'start <id>', etc.
|
|
23
|
+
const verb = shell._lastCommand || 'task-status';
|
|
24
|
+
const status = STATUS_MAP[verb] || args[1];
|
|
25
|
+
|
|
26
|
+
if (!taskId) {
|
|
27
|
+
console.log(chalk.yellow(' Usage: accept <taskId> | start <taskId> | complete <taskId>'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!status) {
|
|
31
|
+
console.log(chalk.yellow(' Usage: task-status <taskId> <accepted|inprogress|completed|rejected>'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const statusColors = {
|
|
36
|
+
accepted: chalk.green,
|
|
37
|
+
inprogress: chalk.blue,
|
|
38
|
+
completed: chalk.green,
|
|
39
|
+
rejected: chalk.red,
|
|
40
|
+
};
|
|
41
|
+
const col = statusColors[status] || chalk.white;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await shell.api._request(
|
|
45
|
+
'POST',
|
|
46
|
+
`/thinknsh/${roomId}/tasks/${taskId}/status`,
|
|
47
|
+
{ status }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
console.log(col(`\n โ Task "${result.taskTitle}" โ ${status}`));
|
|
51
|
+
if (result.allCompleted) {
|
|
52
|
+
console.log(chalk.green(' ๐ All assignees done โ task fully completed!\n'));
|
|
53
|
+
} else {
|
|
54
|
+
console.log('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.log(chalk.red(`\n โ ${err.message}\n`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -1,74 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/tasks/tasks.js โ List my tasks + suggestions in current room
|
|
3
|
+
*/
|
|
1
4
|
const chalk = require('chalk');
|
|
2
5
|
|
|
3
6
|
module.exports = {
|
|
4
|
-
name:
|
|
5
|
-
description:
|
|
6
|
-
aliases:
|
|
7
|
+
name: 'tasks',
|
|
8
|
+
description: 'List your tasks + suggested tasks in the current room',
|
|
9
|
+
aliases: ['mytasks', 'my-tasks'],
|
|
7
10
|
requiresAuth: true,
|
|
11
|
+
requiresRoom: true,
|
|
8
12
|
|
|
9
13
|
async execute(args, shell) {
|
|
10
|
-
const user
|
|
11
|
-
const
|
|
12
|
-
|
|
14
|
+
const user = shell.api.getUser();
|
|
15
|
+
const roomId = shell.ws.getCurrentRoom();
|
|
16
|
+
|
|
17
|
+
if (!user) { console.log(chalk.red('โ Please login first')); return; }
|
|
18
|
+
if (!roomId) { console.log(chalk.yellow('โ ๏ธ Join a room first: join <room-id>')); return; }
|
|
19
|
+
|
|
20
|
+
// โโ Flags โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
21
|
+
const showAll = args.includes('--all') || args.includes('-a');
|
|
22
|
+
const noSuggestions = args.includes('--no-suggest') || args.includes('-ns');
|
|
23
|
+
const filterStatus = args.find(a => ['pending','inprogress','review','completed','reopened'].includes(a));
|
|
24
|
+
const topN = args.find(a => /^\d+$/.test(a)) || 5;
|
|
25
|
+
|
|
26
|
+
// โโ Color maps โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
27
|
+
const stColors = {
|
|
28
|
+
pending: chalk.yellow,
|
|
29
|
+
inprogress: chalk.blue,
|
|
30
|
+
review: chalk.magenta,
|
|
31
|
+
completed: chalk.green,
|
|
32
|
+
reopened: chalk.cyan,
|
|
33
|
+
};
|
|
34
|
+
const prColors = {
|
|
35
|
+
critical: chalk.red,
|
|
36
|
+
high: chalk.hex('#FF6B35'),
|
|
37
|
+
medium: chalk.yellow,
|
|
38
|
+
low: chalk.green,
|
|
39
|
+
};
|
|
40
|
+
const scoreColor = s => s >= 80 ? chalk.red(s) : s >= 60 ? chalk.yellow(s) : chalk.green(s);
|
|
41
|
+
const plColor = pl => ({
|
|
42
|
+
CRITICAL: chalk.red,
|
|
43
|
+
HIGH: chalk.hex('#FF6B35'),
|
|
44
|
+
MEDIUM: chalk.yellow,
|
|
45
|
+
LOW: chalk.green,
|
|
46
|
+
'VERY LOW': chalk.dim,
|
|
47
|
+
}[pl] || chalk.white);
|
|
48
|
+
|
|
49
|
+
// โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
50
|
+
const fmtDue = dueDate => {
|
|
51
|
+
if (!dueDate) return '';
|
|
52
|
+
const d = new Date(dueDate);
|
|
53
|
+
const days = Math.ceil((d - new Date().setHours(0,0,0,0)) / 86400000);
|
|
54
|
+
const str = d.toLocaleDateString('en-IN', { day:'2-digit', month:'short' });
|
|
55
|
+
if (days < 0) return chalk.red(` due:${str}(!)`);
|
|
56
|
+
if (days === 0) return chalk.yellow(` due:TODAY`);
|
|
57
|
+
if (days === 1) return chalk.yellow(` due:${str}`);
|
|
58
|
+
return chalk.dim(` due:${str}`);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const divider = (color = chalk.cyan) => color(' ' + 'โ'.repeat(58));
|
|
62
|
+
const subline = (label, val) =>
|
|
63
|
+
` ${chalk.dim(label+':')} ${chalk.dim(val)}`;
|
|
64
|
+
|
|
65
|
+
// โโ Fetch โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
66
|
+
process.stdout.write(chalk.dim(' Fetching tasks...\r'));
|
|
67
|
+
|
|
68
|
+
let result;
|
|
13
69
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// FIX: getCurrentRoom() lives on shell.ws, not shell.api
|
|
20
|
-
const roomId = shell.ws.getCurrentRoom();
|
|
21
|
-
|
|
22
|
-
if (!roomId) {
|
|
23
|
-
console.log(chalk.yellow('โ ๏ธ Please join a room first: join <room-id>'));
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!shell.api) {
|
|
28
|
-
console.log(chalk.red('โ API not initialized'));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log(chalk.dim(' Fetching tasks...'));
|
|
33
|
-
|
|
34
|
-
// FIX: pass `roomId` (was passing undefined `roomId` from shell.api.getCurrentRoom())
|
|
35
|
-
const result = await shell.api.mytasks(user, token, roomId);
|
|
36
|
-
|
|
37
|
-
if (!result || !result.tasks || result.tasks.length === 0) {
|
|
38
|
-
console.log(chalk.yellow(' No tasks found in this room.'));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
console.log(chalk.cyan(`\n๐ Your Tasks in room: ${chalk.white(roomId)}\n`));
|
|
43
|
-
console.log(chalk.dim('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
44
|
-
|
|
45
|
-
result.tasks.forEach((task, index) => {
|
|
46
|
-
const statusColor = {
|
|
47
|
-
completed: chalk.green,
|
|
48
|
-
in_progress: chalk.yellow,
|
|
49
|
-
pending: chalk.dim,
|
|
50
|
-
}[task.status] || chalk.white;
|
|
51
|
-
|
|
52
|
-
const status = statusColor(`[${task.status || 'pending'}]`);
|
|
53
|
-
const title = chalk.white(task.title || task.name || 'Untitled');
|
|
54
|
-
const due = task.dueDate ? chalk.dim(` โ due: ${new Date(task.dueDate).toLocaleDateString()}`) : '';
|
|
70
|
+
result = await shell.api._request('GET', `/thinknsh/${roomId}/tasks?n=${topN}`);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.log(chalk.red(`\n โ ${err.message}\n`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
55
75
|
|
|
56
|
-
|
|
76
|
+
process.stdout.write(' '.repeat(30) + '\r'); // clear spinner line
|
|
77
|
+
|
|
78
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
79
|
+
// MY TASKS
|
|
80
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
81
|
+
let tasks = result.tasks || [];
|
|
82
|
+
|
|
83
|
+
if (filterStatus) tasks = tasks.filter(t => t.status === filterStatus);
|
|
84
|
+
if (!showAll) tasks = tasks.filter(t => !['completed','done'].includes(t.status));
|
|
85
|
+
|
|
86
|
+
console.log(divider());
|
|
87
|
+
console.log(chalk.cyan(` MY TASKS`) + chalk.dim(` (${tasks.length} active${result.total > tasks.length ? ` of ${result.total}` : ''})`));
|
|
88
|
+
console.log(divider());
|
|
89
|
+
|
|
90
|
+
if (!tasks.length) {
|
|
91
|
+
console.log(chalk.dim('\n No assigned tasks.\n'));
|
|
92
|
+
} else {
|
|
93
|
+
tasks.forEach((task, i) => {
|
|
94
|
+
const sc = stColors[task.status] || chalk.white;
|
|
95
|
+
const pc = prColors[task.priority] || chalk.white;
|
|
96
|
+
|
|
97
|
+
const num = chalk.dim(` ${String(i+1).padStart(2)}.`);
|
|
98
|
+
const status = sc(`[${(task.status || 'unknown').padEnd(10)}]`);
|
|
99
|
+
const title = task.status === 'completed'
|
|
100
|
+
? chalk.dim(task.title)
|
|
101
|
+
: chalk.white(task.title);
|
|
102
|
+
const due = fmtDue(task.dueDate);
|
|
103
|
+
const myStatus = task.myStatus && task.myStatus !== task.status
|
|
104
|
+
? chalk.dim(` (${task.myStatus})`)
|
|
105
|
+
: '';
|
|
106
|
+
const col = task.column ? chalk.dim(` [${task.column}]`) : '';
|
|
107
|
+
|
|
108
|
+
console.log(`\n${num} ${status} ${title}`);
|
|
109
|
+
console.log(subline('id', task._id) +
|
|
110
|
+
` ${chalk.dim('priority:')} ${pc(task.priority)}` +
|
|
111
|
+
due + myStatus + col);
|
|
57
112
|
|
|
58
113
|
if (task.description) {
|
|
59
|
-
|
|
114
|
+
const desc = task.description.length > 90
|
|
115
|
+
? task.description.slice(0, 87) + '...'
|
|
116
|
+
: task.description;
|
|
117
|
+
console.log(` ${chalk.dim(desc)}`);
|
|
60
118
|
}
|
|
61
119
|
});
|
|
120
|
+
}
|
|
62
121
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
122
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
123
|
+
// SUGGESTIONS
|
|
124
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
125
|
+
if (!noSuggestions) {
|
|
126
|
+
const suggestions = result.suggestions?.tasks || [];
|
|
127
|
+
const insights = result.suggestions?.insights || {};
|
|
128
|
+
const meta = result.suggestions?.metadata || {};
|
|
129
|
+
|
|
130
|
+
console.log('\n' + divider(chalk.magenta));
|
|
131
|
+
console.log(chalk.magenta(` SUGGESTED FOR YOU`) +
|
|
132
|
+
chalk.dim(` (top ${suggestions.length}${meta.totalScored ? ` of ${meta.totalScored} available` : ''})`));
|
|
133
|
+
console.log(divider(chalk.magenta));
|
|
134
|
+
|
|
135
|
+
if (!suggestions.length) {
|
|
136
|
+
console.log(chalk.dim('\n No suggestions โ all tasks are assigned.\n'));
|
|
69
137
|
} else {
|
|
70
|
-
|
|
138
|
+
suggestions.forEach((task, i) => {
|
|
139
|
+
const pc = prColors[task.priority] || chalk.white;
|
|
140
|
+
const plc = plColor(task.priorityLevel);
|
|
141
|
+
const due = fmtDue(task.dueDate);
|
|
142
|
+
const col = task.column ? chalk.dim(` [${task.column}]`) : '';
|
|
143
|
+
const assignees = task.assigneeCount
|
|
144
|
+
? chalk.dim(` ยท ${task.assigneeCount} assigned`)
|
|
145
|
+
: chalk.dim(' ยท unassigned');
|
|
146
|
+
const urgentTag = task.isOverdue
|
|
147
|
+
? chalk.red(' โ OVERDUE')
|
|
148
|
+
: task.isUrgent ? chalk.yellow(' โฐ URGENT') : '';
|
|
149
|
+
|
|
150
|
+
const num = chalk.dim(` ${String(i+1).padStart(2)}.`);
|
|
151
|
+
const score = `[score:${scoreColor(task.score)}]`;
|
|
152
|
+
const level = plc(`[${task.priorityLevel}]`);
|
|
153
|
+
|
|
154
|
+
console.log(`\n${num} ${score} ${level} ${chalk.white(task.title)}${urgentTag}`);
|
|
155
|
+
console.log(subline('id', task._id) +
|
|
156
|
+
` ${chalk.dim('priority:')} ${pc(task.priority)}` +
|
|
157
|
+
due + col + assignees);
|
|
158
|
+
|
|
159
|
+
if (task.priorityReason) {
|
|
160
|
+
console.log(` ${chalk.dim('โณ ' + task.priorityReason)}`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// โโ Insights bar โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
165
|
+
console.log('\n' + divider(chalk.dim));
|
|
166
|
+
if (insights.focusRecommendation) {
|
|
167
|
+
console.log(` ${chalk.magenta('๐ก')} ${chalk.white(insights.focusRecommendation)}`);
|
|
168
|
+
}
|
|
169
|
+
if (insights.estimatedTime) {
|
|
170
|
+
console.log(` ${chalk.dim('โฑ Est. time:')} ${chalk.white(insights.estimatedTime)}`);
|
|
171
|
+
}
|
|
172
|
+
if (meta.scoreRange) {
|
|
173
|
+
console.log(` ${chalk.dim('๐ Score range:')} ${chalk.white(`${meta.scoreRange.min}โ${meta.scoreRange.max}`)} ${chalk.dim('avg:')} ${chalk.white(meta.scoreRange.avg)}`);
|
|
174
|
+
}
|
|
71
175
|
}
|
|
72
176
|
}
|
|
177
|
+
|
|
178
|
+
// โโ Footer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
179
|
+
console.log('\n' + divider());
|
|
180
|
+
console.log(chalk.dim(' task <id> ยท complete <id> ยท tasks --all ยท tasks 10 ยท tasks --no-suggest'));
|
|
181
|
+
console.log(divider() + '\n');
|
|
73
182
|
}
|
|
74
183
|
};
|
package/src/core/api-client.js
CHANGED
|
@@ -115,37 +115,73 @@ class ThinkNCollabAPI {
|
|
|
115
115
|
|
|
116
116
|
// โโโ Auth โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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, // โ
add karo
|
|
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
|
+
}
|
|
124
143
|
|
|
125
|
-
|
|
126
|
-
this._saveSession({
|
|
127
|
-
token: result.shellToken,
|
|
128
|
-
user: {
|
|
129
|
-
_id: result._id,
|
|
130
|
-
userId: result._id, // explicit โ ws needs this
|
|
131
|
-
email: result.email,
|
|
132
|
-
name: result.name,
|
|
133
|
-
userType: result.userType || result.model || 'User',
|
|
134
|
-
},
|
|
135
|
-
timestamp: new Date().toISOString()
|
|
136
|
-
});
|
|
144
|
+
// โโโ Session event logger โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
137
145
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
168
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
169
|
+
stopHeartbeat() {
|
|
170
|
+
if (this._heartbeatTimer) {
|
|
171
|
+
clearInterval(this._heartbeatTimer);
|
|
172
|
+
this._heartbeatTimer = null;
|
|
147
173
|
}
|
|
174
|
+
}
|
|
148
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
|
+
}
|
|
149
185
|
async whoami() {
|
|
150
186
|
if (!this.isAuthenticated()) throw new Error('Not logged in');
|
|
151
187
|
return this.getUser();
|
|
@@ -18,7 +18,7 @@ const fs = require('fs-extra');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const os = require('os');
|
|
20
20
|
|
|
21
|
-
const CONFIG_FILE = '
|
|
21
|
+
const CONFIG_FILE = 'tncproject';
|
|
22
22
|
const MAGIC = Buffer.from('TNCP');
|
|
23
23
|
const ROOM_ID_SIZE = 24;
|
|
24
24
|
const MIN_SIZE = 4 + 1 + ROOM_ID_SIZE + 12 + 16 + 2; // 59 bytes minimum
|
|
@@ -40,7 +40,7 @@ function findConfigFile(startDir = process.cwd()) {
|
|
|
40
40
|
* Read and lightly validate a .tncproject file.
|
|
41
41
|
* Returns { filePath, roomId, fileBase64 } โ no decryption.
|
|
42
42
|
*
|
|
43
|
-
* @throws if file is missing, wrong format, or roomId looks wrong
|
|
43
|
+
* @throws if file is missing, wrong format, or roomId looks wrong http://localhost:3001
|
|
44
44
|
*/
|
|
45
45
|
function loadProjectConfig(startDir = process.cwd()) {
|
|
46
46
|
const filePath = findConfigFile(startDir);
|