thinkncollab-shell 0.0.5 → 0.0.7
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 +3 -3
- package/package.json +1 -1
- package/src/commands/auth/login.js +58 -58
- package/src/commands/auth/register.js +52 -0
- package/src/commands/auth/whoami.js +44 -39
- package/src/commands/index.js +69 -43
- package/src/commands/room/join.js +119 -45
- package/src/commands/shell/notify.js +88 -0
- package/src/commands/tasks/complete.js +47 -0
- package/src/commands/tasks/create-task.js +101 -0
- package/src/commands/tasks/suggestTask.js +0 -0
- package/src/commands/tasks/task-status.js +61 -0
- package/src/commands/tasks/task.js +75 -0
- package/src/commands/tasks/tasks.js +183 -0
- package/src/commands/terminal/share.js +252 -0
- package/src/core/api-client.js +156 -66
- package/src/core/notification-window.js +1 -1
- package/src/core/project-config.js +100 -0
- package/src/core/shell.js +252 -221
- package/src/core/websocket.js +136 -149
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/shell/notify.js — Send notifications to room
|
|
3
|
+
*/
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: 'notify',
|
|
8
|
+
description: 'Send a notification to the current room',
|
|
9
|
+
aliases: ['notif'],
|
|
10
|
+
requiresAuth: true,
|
|
11
|
+
requiresRoom: true,
|
|
12
|
+
|
|
13
|
+
// Usage:
|
|
14
|
+
// notify info <message>
|
|
15
|
+
// notify warn <message>
|
|
16
|
+
// notify error <message>
|
|
17
|
+
// notify success <message>
|
|
18
|
+
// notify <message> ← defaults to 'info'
|
|
19
|
+
|
|
20
|
+
async execute(args, shell) {
|
|
21
|
+
if (!args.length) {
|
|
22
|
+
console.log(chalk.yellow('Usage: notify <level> <message>'));
|
|
23
|
+
console.log(chalk.dim(' Levels: info | warn | error | success'));
|
|
24
|
+
console.log(chalk.dim(' Example: notify info deployment done'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const levels = ['info', 'warn', 'warning', 'error', 'success'];
|
|
29
|
+
let level, message;
|
|
30
|
+
|
|
31
|
+
if (levels.includes(args[0].toLowerCase())) {
|
|
32
|
+
level = args[0].toLowerCase() === 'warning' ? 'warn' : args[0].toLowerCase();
|
|
33
|
+
message = args.slice(1).join(' ');
|
|
34
|
+
} else {
|
|
35
|
+
level = 'info';
|
|
36
|
+
message = args.join(' ');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!message.trim()) {
|
|
40
|
+
console.log(chalk.red('❌ Message cannot be empty'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const levelColors = {
|
|
45
|
+
info: chalk.blue,
|
|
46
|
+
warn: chalk.yellow,
|
|
47
|
+
error: chalk.red,
|
|
48
|
+
success: chalk.green,
|
|
49
|
+
};
|
|
50
|
+
const levelIcons = {
|
|
51
|
+
info: 'ℹ️ ',
|
|
52
|
+
warn: '⚠️ ',
|
|
53
|
+
error: '❌',
|
|
54
|
+
success: '✅',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const color = levelColors[level] || chalk.blue;
|
|
58
|
+
const icon = levelIcons[level] || 'ℹ️ ';
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await shell.ws.sendNotification(
|
|
62
|
+
{
|
|
63
|
+
type: level,
|
|
64
|
+
title: `${level.charAt(0).toUpperCase() + level.slice(1)} from terminal`,
|
|
65
|
+
message: message,
|
|
66
|
+
data: {
|
|
67
|
+
sentBy: shell.api.getUser()?.name || 'unknown',
|
|
68
|
+
via: 'thinknsh',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
'room'
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Also push to local notification window
|
|
75
|
+
shell.pushNotification({
|
|
76
|
+
type: 'notification',
|
|
77
|
+
level: level,
|
|
78
|
+
title: `Notify — ${level}`,
|
|
79
|
+
message: message,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log(color(`${icon} Notification sent → [${level}] ${message}`));
|
|
83
|
+
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.log(chalk.red(`❌ Failed to send notification: ${err.message}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/tasks/complete.js — Mark your task assignment as completed
|
|
3
|
+
*/
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: 'complete',
|
|
8
|
+
description: 'Mark a task as completed',
|
|
9
|
+
aliases: ['done', 'finish', 'complete'],
|
|
10
|
+
requiresAuth: true,
|
|
11
|
+
|
|
12
|
+
async execute(args, shell) {
|
|
13
|
+
if (!args[0]) {
|
|
14
|
+
console.log(chalk.red('Usage: complete <task-id>'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const taskId = args[0];
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const result = await shell.api._request('POST', `/thinknsh/${taskId}/cli/complete`);
|
|
22
|
+
|
|
23
|
+
console.log(chalk.green(`✅ Task completed: "${result.task?.title || taskId}"`));
|
|
24
|
+
|
|
25
|
+
if (result.allCompleted)
|
|
26
|
+
console.log(chalk.green(' 🎉 All assignees have completed this task!'));
|
|
27
|
+
|
|
28
|
+
// Push to notification window
|
|
29
|
+
shell.pushNotification({
|
|
30
|
+
type: 'notification',
|
|
31
|
+
level: 'success',
|
|
32
|
+
title: 'Task Completed',
|
|
33
|
+
message: result.task?.title || taskId,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err.message?.includes('403'))
|
|
38
|
+
console.log(chalk.red('❌ You are not assigned to this task'));
|
|
39
|
+
else if (err.message?.includes('404'))
|
|
40
|
+
console.log(chalk.red('❌ Task not found'));
|
|
41
|
+
else if (err.message?.includes('already'))
|
|
42
|
+
console.log(chalk.yellow('⚠️ Task already completed'));
|
|
43
|
+
else
|
|
44
|
+
console.log(chalk.red(`❌ ${err.message}`));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/tasks/create-task.js — Create a new task interactively
|
|
3
|
+
*/
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: 'create-task',
|
|
8
|
+
description: 'Create a new task in current room',
|
|
9
|
+
aliases: ['newtask', 'addtask'],
|
|
10
|
+
requiresAuth: true,
|
|
11
|
+
requiresRoom: true,
|
|
12
|
+
|
|
13
|
+
async execute(args, shell) {
|
|
14
|
+
const { input, select, confirm } = require('@inquirer/prompts');
|
|
15
|
+
const roomId = shell.ws.getCurrentRoom();
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Fetch columns of current room's board
|
|
19
|
+
const { columns } = await shell.api._request('GET', `/rooms/${roomId}/cli/columns`);
|
|
20
|
+
if (!columns?.length) {
|
|
21
|
+
console.log(chalk.red('❌ No columns found in this room\'s board'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(chalk.cyan('\n // create-task\n'));
|
|
26
|
+
|
|
27
|
+
const title = await input({
|
|
28
|
+
message: 'Task title:',
|
|
29
|
+
validate: v => v.trim().length >= 3 || 'Min 3 characters'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const description = await input({ message: 'Description (optional):' });
|
|
33
|
+
|
|
34
|
+
const columnId = await select({
|
|
35
|
+
message: 'Column:',
|
|
36
|
+
choices: columns.map(c => ({ name: c.title, value: c._id }))
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const priority = await select({
|
|
40
|
+
message: 'Priority:',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'low', value: 'low' },
|
|
43
|
+
{ name: 'medium', value: 'medium' },
|
|
44
|
+
{ name: 'high', value: 'high' },
|
|
45
|
+
],
|
|
46
|
+
default: 'medium'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const categories = [
|
|
50
|
+
'Bugs', 'Feature Requests', 'Improvements / Enhancements',
|
|
51
|
+
'Production Critical Issues', 'Security Issues', 'Performance Issues',
|
|
52
|
+
'UI/UX Issues', 'Testing & QA', 'DevOps & Infrastructure',
|
|
53
|
+
'Refactoring & Code Quality', 'Documentation', 'Other'
|
|
54
|
+
];
|
|
55
|
+
const category = await select({
|
|
56
|
+
message: 'Category:',
|
|
57
|
+
choices: categories.map(c => ({ name: c, value: c }))
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const dueDateStr = await input({
|
|
61
|
+
message: 'Due date (YYYY-MM-DD, optional):',
|
|
62
|
+
validate: v => {
|
|
63
|
+
if (!v) return true;
|
|
64
|
+
return !isNaN(Date.parse(v)) || 'Invalid date format';
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const confirmed = await confirm({ message: 'Create task?', default: true });
|
|
69
|
+
if (!confirmed) { console.log(chalk.dim(' Cancelled.')); return; }
|
|
70
|
+
|
|
71
|
+
const payload = {
|
|
72
|
+
title: title.trim(),
|
|
73
|
+
description: description.trim() || undefined,
|
|
74
|
+
columnId,
|
|
75
|
+
priority,
|
|
76
|
+
category: category === 'Other' ? undefined : category,
|
|
77
|
+
dueDate: dueDateStr ? new Date(dueDateStr).toISOString() : undefined,
|
|
78
|
+
source: 'cli',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = await shell.api._request('POST', `/${roomId}/cli/tasks`, payload);
|
|
82
|
+
|
|
83
|
+
console.log(chalk.green(`\n✅ Task created: "${result.task.title}"`));
|
|
84
|
+
console.log(chalk.dim(` ID: ${result.task._id}`));
|
|
85
|
+
console.log(chalk.dim(` Column: ${result.task.columnId?.title || columnId}\n`));
|
|
86
|
+
|
|
87
|
+
shell.pushNotification({
|
|
88
|
+
type: 'notification',
|
|
89
|
+
level: 'success',
|
|
90
|
+
title: 'Task Created',
|
|
91
|
+
message: result.task.title,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (err.message?.includes('403'))
|
|
96
|
+
console.log(chalk.red('❌ You are not a member of this room'));
|
|
97
|
+
else
|
|
98
|
+
console.log(chalk.red(`❌ ${err.message}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
File without changes
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/tasks/task.js — View single task detail
|
|
3
|
+
*/
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: 'task',
|
|
8
|
+
description: 'View task details by ID',
|
|
9
|
+
aliases: ['t'],
|
|
10
|
+
requiresAuth: true,
|
|
11
|
+
|
|
12
|
+
async execute(args, shell) {
|
|
13
|
+
if (!args[0]) {
|
|
14
|
+
console.log(chalk.red('Usage: task <task-id>'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const taskId = args[0];
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const task = await shell.api._request('GET', `/thinknsh/${taskId}/cli`);
|
|
22
|
+
|
|
23
|
+
const stColors = {
|
|
24
|
+
pending: chalk.yellow,
|
|
25
|
+
inprogress: chalk.blue,
|
|
26
|
+
review: chalk.magenta,
|
|
27
|
+
completed: chalk.green,
|
|
28
|
+
reopened: chalk.cyan,
|
|
29
|
+
};
|
|
30
|
+
const prColors = { high: chalk.red, medium: chalk.yellow, low: chalk.green };
|
|
31
|
+
const stColor = stColors[task.status] || chalk.white;
|
|
32
|
+
const prColor = prColors[task.priority] || chalk.white;
|
|
33
|
+
|
|
34
|
+
console.log(chalk.cyan('\n ─── Task ────────────────────────────────────────'));
|
|
35
|
+
console.log(` ${chalk.dim('id')} ${chalk.dim(task._id)}`);
|
|
36
|
+
console.log(` ${chalk.dim('title')} ${chalk.bold(task.title)}`);
|
|
37
|
+
console.log(` ${chalk.dim('status')} ${stColor(task.status)}`);
|
|
38
|
+
console.log(` ${chalk.dim('priority')} ${prColor(task.priority)}`);
|
|
39
|
+
if (task.category)
|
|
40
|
+
console.log(` ${chalk.dim('category')} ${chalk.dim(task.category)}`);
|
|
41
|
+
if (task.dueDate)
|
|
42
|
+
console.log(` ${chalk.dim('due')} ${chalk.yellow(new Date(task.dueDate).toLocaleDateString())}`);
|
|
43
|
+
if (task.description) {
|
|
44
|
+
console.log(chalk.dim('\n description:'));
|
|
45
|
+
task.description.split('\n').forEach(l => console.log(` ${chalk.white(l)}`));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (task.assignedTo?.length) {
|
|
49
|
+
console.log(chalk.dim('\n assignees:'));
|
|
50
|
+
task.assignedTo.forEach(a => {
|
|
51
|
+
const name = a.userId?.name || 'Unknown';
|
|
52
|
+
const stBadge = a.status === 'completed' ? chalk.green('✓') :
|
|
53
|
+
a.status === 'accepted' ? chalk.blue('●') :
|
|
54
|
+
a.status === 'rejected' ? chalk.red('✗') : chalk.dim('○');
|
|
55
|
+
console.log(` ${stBadge} ${name} ${chalk.dim(`[${a.status}]`)}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (task.columnId?.name || task.columnName)
|
|
60
|
+
console.log(`\n ${chalk.dim('column')} ${task.columnId?.name || task.columnName}`);
|
|
61
|
+
if (task.boardName)
|
|
62
|
+
console.log(` ${chalk.dim('board')} ${task.boardName}`);
|
|
63
|
+
|
|
64
|
+
console.log(chalk.cyan(' ─────────────────────────────────────────────────\n'));
|
|
65
|
+
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err.message?.includes('403'))
|
|
68
|
+
console.log(chalk.red('❌ Access denied — you are not a member of this room'));
|
|
69
|
+
else if (err.message?.includes('404'))
|
|
70
|
+
console.log(chalk.red('❌ Task not found'));
|
|
71
|
+
else
|
|
72
|
+
console.log(chalk.red(`❌ ${err.message}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/tasks/tasks.js — List my tasks + suggestions in current room
|
|
3
|
+
*/
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: 'tasks',
|
|
8
|
+
description: 'List your tasks + suggested tasks in the current room',
|
|
9
|
+
aliases: ['mytasks', 'my-tasks'],
|
|
10
|
+
requiresAuth: true,
|
|
11
|
+
requiresRoom: true,
|
|
12
|
+
|
|
13
|
+
async execute(args, shell) {
|
|
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;
|
|
69
|
+
try {
|
|
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
|
+
}
|
|
75
|
+
|
|
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);
|
|
112
|
+
|
|
113
|
+
if (task.description) {
|
|
114
|
+
const desc = task.description.length > 90
|
|
115
|
+
? task.description.slice(0, 87) + '...'
|
|
116
|
+
: task.description;
|
|
117
|
+
console.log(` ${chalk.dim(desc)}`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
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'));
|
|
137
|
+
} else {
|
|
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
|
+
}
|
|
175
|
+
}
|
|
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');
|
|
182
|
+
}
|
|
183
|
+
};
|