thinkncollab-shell 0.0.4 → 0.0.6
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 +392 -16
- package/package.json +1 -1
- package/src/commands/auth/login.js +58 -58
- package/src/commands/auth/register.js +52 -0
- package/src/commands/index.js +62 -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.js +75 -0
- package/src/commands/tasks/tasks.js +74 -0
- package/src/commands/terminal/share.js +252 -0
- package/src/core/api-client.js +104 -50
- package/src/core/notification-window.js +2 -1
- package/src/core/project-config.js +100 -0
- package/src/core/shell.js +222 -210
- package/src/core/websocket.js +166 -101
package/src/commands/index.js
CHANGED
|
@@ -1,54 +1,77 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Command Registry
|
|
2
|
+
* src/commands/index.js — Command Registry
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
4
|
const chalk = require('chalk');
|
|
6
5
|
|
|
7
6
|
class CommandRegistry {
|
|
8
7
|
constructor(shell) {
|
|
9
|
-
this.shell
|
|
10
|
-
this.commands
|
|
11
|
-
this.aliases
|
|
8
|
+
this.shell = shell;
|
|
9
|
+
this.commands = new Map();
|
|
10
|
+
this.aliases = new Map();
|
|
12
11
|
this.categories = new Map();
|
|
13
|
-
|
|
14
12
|
this.loadCommands();
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
loadCommands() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.register(require('./team/invite'), 'team');
|
|
25
|
-
this.register(require('./room/join'), 'room');
|
|
26
|
-
this.register(require('./room/leave'), 'room');
|
|
27
|
-
this.register(require('./room/rooms'), 'room');
|
|
28
|
-
this.register(require('./message/say'), 'message');
|
|
29
|
-
this.register(require('./shell/status'), 'shell');
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error(chalk.red(`❌ Failed to load command: ${error.message}`));
|
|
32
|
-
console.error(error.stack);
|
|
16
|
+
const load = (path, category) => {
|
|
17
|
+
try {
|
|
18
|
+
const cmd = require(path);
|
|
19
|
+
if (!cmd || !cmd.name) {
|
|
20
|
+
console.error(chalk.red(`❌ BAD COMMAND (no name): ${path}`));
|
|
21
|
+
return;
|
|
33
22
|
}
|
|
23
|
+
this.register(cmd, category);
|
|
24
|
+
}
|
|
25
|
+
catch (e) { console.error(chalk.red(`❌ Failed to load ${path}: ${e.message}`)); }
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Auth
|
|
29
|
+
load('./auth/login', 'auth');
|
|
30
|
+
load('./auth/logout', 'auth');
|
|
31
|
+
load('./auth/whoami', 'auth');
|
|
32
|
+
load('./auth/register', 'auth');
|
|
33
|
+
|
|
34
|
+
// Team
|
|
35
|
+
load('./team/myteam', 'team');
|
|
36
|
+
load('./team/teammates', 'team');
|
|
37
|
+
load('./team/invite', 'team');
|
|
38
|
+
|
|
39
|
+
// Room
|
|
40
|
+
load('./room/join', 'room');
|
|
41
|
+
load('./room/leave', 'room');
|
|
42
|
+
load('./room/rooms', 'room');
|
|
43
|
+
|
|
44
|
+
// Message
|
|
45
|
+
load('./message/say', 'message');
|
|
46
|
+
|
|
47
|
+
// Shell
|
|
48
|
+
load('./shell/status', 'shell');
|
|
49
|
+
load('./shell/notify', 'shell');
|
|
50
|
+
|
|
51
|
+
// Tasks
|
|
52
|
+
load('./tasks/tasks', 'tasks');
|
|
53
|
+
load('./tasks/task', 'tasks');
|
|
54
|
+
// load('./tasks/start', 'tasks');
|
|
55
|
+
load('./tasks/complete', 'tasks');
|
|
56
|
+
// load('./tasks/comment', 'tasks');
|
|
57
|
+
load('./tasks/create-task', 'tasks');
|
|
58
|
+
|
|
59
|
+
// Terminal
|
|
60
|
+
load('./terminal/share', 'terminal');
|
|
34
61
|
}
|
|
35
62
|
|
|
36
63
|
register(command, category) {
|
|
64
|
+
if (!command || !command.name) {
|
|
65
|
+
console.error(chalk.yellow(`⚠️ Skipping invalid command in category: ${category}`));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
37
68
|
this.commands.set(command.name, command);
|
|
38
|
-
|
|
39
|
-
if (!this.categories.has(category)) {
|
|
40
|
-
this.categories.set(category, []);
|
|
41
|
-
}
|
|
69
|
+
if (!this.categories.has(category)) this.categories.set(category, []);
|
|
42
70
|
this.categories.get(category).push(command.name);
|
|
43
|
-
|
|
44
|
-
if (command.aliases) {
|
|
45
|
-
command.aliases.forEach(alias => this.aliases.set(alias, command.name));
|
|
46
|
-
}
|
|
71
|
+
if (command.aliases) command.aliases.forEach(a => this.aliases.set(a, command.name));
|
|
47
72
|
}
|
|
48
73
|
|
|
49
|
-
addAlias(alias, command) {
|
|
50
|
-
this.aliases.set(alias, command);
|
|
51
|
-
}
|
|
74
|
+
addAlias(alias, command) { this.aliases.set(alias, command); }
|
|
52
75
|
|
|
53
76
|
getCommand(name) {
|
|
54
77
|
if (this.aliases.has(name)) name = this.aliases.get(name);
|
|
@@ -65,17 +88,14 @@ class CommandRegistry {
|
|
|
65
88
|
|
|
66
89
|
try {
|
|
67
90
|
if (command.requiresAuth && !shell.api?.isAuthenticated()) {
|
|
68
|
-
console.log(chalk.red('❌ Please login first: login
|
|
91
|
+
console.log(chalk.red('❌ Please login first: login'));
|
|
69
92
|
return true;
|
|
70
93
|
}
|
|
71
|
-
|
|
72
|
-
if (command.requiresRoom && !shell.api?.getCurrentRoom()) {
|
|
94
|
+
if (command.requiresRoom && !shell.ws?.getCurrentRoom()) {
|
|
73
95
|
console.log(chalk.red('❌ Please join a room first: join <room-id>'));
|
|
74
96
|
return true;
|
|
75
97
|
}
|
|
76
|
-
|
|
77
98
|
await command.execute(cmdArgs, shell);
|
|
78
|
-
|
|
79
99
|
} catch (error) {
|
|
80
100
|
console.log(chalk.red(`❌ Command error: ${error.message}`));
|
|
81
101
|
}
|
|
@@ -86,17 +106,16 @@ class CommandRegistry {
|
|
|
86
106
|
getHelpText() {
|
|
87
107
|
let help = chalk.cyan('\n📚 Available Commands:\n');
|
|
88
108
|
help += chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n');
|
|
89
|
-
|
|
90
109
|
for (const [category, commands] of this.categories) {
|
|
91
110
|
help += chalk.yellow(`${category.toUpperCase()}:\n`);
|
|
92
111
|
commands.forEach(cmdName => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
const cmd = this.commands.get(cmdName);
|
|
113
|
+
if (!cmd || !cmdName) return; // ← ye line add karo
|
|
114
|
+
const aliases = cmd.aliases ? chalk.dim(` (${cmd.aliases.join(', ')})`) : '';
|
|
115
|
+
help += ` ${chalk.green(cmdName.padEnd(14))}${aliases} - ${cmd.description || ''}\n`;
|
|
116
|
+
});
|
|
97
117
|
help += '\n';
|
|
98
118
|
}
|
|
99
|
-
|
|
100
119
|
return help;
|
|
101
120
|
}
|
|
102
121
|
}
|
|
@@ -1,68 +1,142 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Join Command
|
|
3
|
-
*
|
|
4
|
-
* Usage: join <room-id>
|
|
5
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Usage: join <room-id> [password]
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* 1. REST call → shell.api.joinRoom() — validates membership + fetches room info
|
|
8
|
+
* 2. WebSocket → thinknsh:join — registers socket in the room on the server
|
|
9
|
+
* 3. Wait for → thinknsh:joined — server confirms + sends message history
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
const chalk = require('chalk');
|
|
9
13
|
|
|
14
|
+
// How long to wait for thinknsh:joined after emitting thinknsh:join (ms)
|
|
15
|
+
const JOIN_TIMEOUT_MS = 8000;
|
|
16
|
+
|
|
10
17
|
module.exports = {
|
|
11
|
-
name:
|
|
12
|
-
description:
|
|
13
|
-
aliases:
|
|
18
|
+
name: 'join',
|
|
19
|
+
description: 'Join a collaboration room',
|
|
20
|
+
aliases: ['enter', 'goto'],
|
|
14
21
|
requiresAuth: true,
|
|
15
|
-
|
|
22
|
+
|
|
16
23
|
async execute(args, shell) {
|
|
17
24
|
if (args.length === 0) {
|
|
18
25
|
console.log(chalk.red('❌ Usage: join <room-id> [password]'));
|
|
19
26
|
return;
|
|
20
27
|
}
|
|
21
|
-
|
|
22
|
-
const roomId
|
|
28
|
+
|
|
29
|
+
const roomId = args[0];
|
|
23
30
|
const password = args[1];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.log(chalk.dim(` 📝 ${result.room.description || 'No description'}`));
|
|
34
|
-
console.log(chalk.dim(` 👥 ${result.participants.length} participants`));
|
|
35
|
-
|
|
36
|
-
// Show recent messages
|
|
37
|
-
if (result.recentMessages && result.recentMessages.length > 0) {
|
|
38
|
-
console.log(chalk.cyan('\n📜 Recent messages:'));
|
|
39
|
-
result.recentMessages.slice(-3).forEach(msg => {
|
|
40
|
-
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
41
|
-
console.log(chalk.dim(` [${time}] ${msg.username}: ${msg.message}`));
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Show participants
|
|
46
|
-
if (result.participants.length > 0) {
|
|
47
|
-
console.log(chalk.cyan('\n👥 In room:'));
|
|
48
|
-
const online = result.participants.filter(p => p.online);
|
|
49
|
-
online.forEach(p => {
|
|
50
|
-
const you = p.email === shell.api.getUser().email ? chalk.green(' (you)') : '';
|
|
51
|
-
console.log(` ${chalk.green('●')} ${p.name || p.email}${you}`);
|
|
52
|
-
});
|
|
31
|
+
|
|
32
|
+
// ── Step 1: auto-connect WebSocket if needed ──────────────────────
|
|
33
|
+
if (!shell.ws.isConnected()) {
|
|
34
|
+
console.log(chalk.dim(' Connecting...'));
|
|
35
|
+
try {
|
|
36
|
+
await shell.ws.connect();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.log(chalk.red(`❌ WebSocket connect failed: ${err.message}`));
|
|
39
|
+
return;
|
|
53
40
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(chalk.cyan(`🔗 Joining room ${chalk.bold(roomId)}...`));
|
|
44
|
+
|
|
45
|
+
// ── Step 2: REST call — validates membership, returns room meta ───
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = await shell.api.joinRoom(roomId, password);
|
|
57
49
|
} catch (error) {
|
|
58
|
-
if (error.message.includes('password')) {
|
|
59
|
-
console.log(chalk.red(`❌ Room requires password: join ${roomId} <password>`));
|
|
60
|
-
} else if (error.message.includes('not found')) {
|
|
50
|
+
if (error.message?.toLowerCase().includes('password')) {
|
|
51
|
+
console.log(chalk.red(`❌ Room requires a password: join ${roomId} <password>`));
|
|
52
|
+
} else if (error.message?.toLowerCase().includes('not found')) {
|
|
61
53
|
console.log(chalk.red(`❌ Room not found: ${roomId}`));
|
|
62
|
-
console.log(chalk.dim('
|
|
54
|
+
console.log(chalk.dim(' Type "rooms" to see available rooms'));
|
|
55
|
+
} else if (error.message?.toLowerCase().includes('not a member') ||
|
|
56
|
+
error.message?.toLowerCase().includes('unauthorized')) {
|
|
57
|
+
console.log(chalk.red(`❌ You are not a member of this room`));
|
|
63
58
|
} else {
|
|
64
59
|
console.log(chalk.red(`❌ Failed to join: ${error.message}`));
|
|
65
60
|
}
|
|
61
|
+
return;
|
|
66
62
|
}
|
|
63
|
+
|
|
64
|
+
const room = result.room || result;
|
|
65
|
+
const user = shell.api.getUser?.() || {};
|
|
66
|
+
|
|
67
|
+
// ── Step 3: emit thinknsh:join and wait for thinknsh:joined ──────
|
|
68
|
+
await new Promise((resolve) => {
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
shell.ws.socket?.off('thinknsh:joined', onJoined);
|
|
71
|
+
shell.ws.socket?.off('thinknsh:error', onError);
|
|
72
|
+
console.log(chalk.red('❌ Join timeout — server did not confirm. Try again.'));
|
|
73
|
+
resolve();
|
|
74
|
+
}, JOIN_TIMEOUT_MS);
|
|
75
|
+
|
|
76
|
+
function onJoined(data) {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
shell.ws.socket?.off('thinknsh:error', onError);
|
|
79
|
+
|
|
80
|
+
if (!data.success) {
|
|
81
|
+
console.log(chalk.red(`❌ Join rejected: ${data.message || 'unknown error'}`));
|
|
82
|
+
resolve();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Success output ────────────────────────────────────────
|
|
87
|
+
console.log(chalk.green(`✅ Joined ${chalk.bold(room.name || roomId)}`));
|
|
88
|
+
|
|
89
|
+
if (room.description)
|
|
90
|
+
console.log(chalk.dim(` 📝 ${room.description}`));
|
|
91
|
+
|
|
92
|
+
const participants = result.participants || [];
|
|
93
|
+
if (participants.length)
|
|
94
|
+
console.log(chalk.dim(` 👥 ${participants.length} participant(s)`));
|
|
95
|
+
|
|
96
|
+
// Recent message history from thinknsh:joined payload
|
|
97
|
+
const history = data.history || [];
|
|
98
|
+
if (history.length) {
|
|
99
|
+
console.log(chalk.cyan('\n📜 Recent messages:'));
|
|
100
|
+
history.slice(-5).forEach(msg => {
|
|
101
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
102
|
+
const isYou = msg.userId === user._id?.toString() || msg.userId === user.id?.toString();
|
|
103
|
+
const who = isYou ? chalk.green('you') : chalk.cyan(msg.name || 'Unknown');
|
|
104
|
+
const src = msg.source === 'shell' ? chalk.dim('[shell]') : '';
|
|
105
|
+
console.log(` ${chalk.dim(`[${time}]`)} ${who}${src}: ${msg.message}`);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Online participants
|
|
110
|
+
const online = participants.filter(p => p.online);
|
|
111
|
+
if (online.length) {
|
|
112
|
+
console.log(chalk.cyan('\n👥 Online:'));
|
|
113
|
+
online.forEach(p => {
|
|
114
|
+
const isYou = p.email === user.email ? chalk.green(' (you)') : '';
|
|
115
|
+
console.log(` ${chalk.green('●')} ${p.name || p.email}${isYou}`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(chalk.dim('\n💡 say <message> to chat | leave to exit\n'));
|
|
120
|
+
resolve();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function onError(data) {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
shell.ws.socket?.off('thinknsh:joined', onJoined);
|
|
126
|
+
console.log(chalk.red(`❌ ${data.message || 'Join error'}`));
|
|
127
|
+
resolve();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
shell.ws.socket.once('thinknsh:joined', onJoined);
|
|
131
|
+
shell.ws.socket.once('thinknsh:error', onError);
|
|
132
|
+
|
|
133
|
+
// Emit the WebSocket join event
|
|
134
|
+
shell.ws.socket.emit('thinknsh:join', {
|
|
135
|
+
roomId,
|
|
136
|
+
userId: user._id?.toString() || user.id?.toString(),
|
|
137
|
+
name: user.name || user.username || 'Unknown',
|
|
138
|
+
userType: user.model || user.role || 'User',
|
|
139
|
+
});
|
|
140
|
+
});
|
|
67
141
|
}
|
|
68
142
|
};
|
|
@@ -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'],
|
|
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,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
|
+
};
|