thinkncollab-shell 0.0.9 → 0.0.12
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/package.json +5 -2
- package/src/commands/message/say.js +21 -15
- package/src/commands/tasks/complete.js +563 -43
- package/src/commands/tasks/create-task.js +2 -2
- package/src/commands/tasks/task.js +162 -65
- package/src/commands/tasks/tasks.js +4 -3
- package/src/core/api-client.js +1 -1
- package/src/core/shell.js +339 -301
- package/src/core/websocket.js +17 -13
- package/src/events/notifications.js +3 -4
|
@@ -1,75 +1,172 @@
|
|
|
1
1
|
/*
|
|
2
|
-
src/commands/tasks/task.js — View
|
|
3
|
-
|
|
2
|
+
src/commands/tasks/task-info.js — View task details + testConfig + webhook URL
|
|
3
|
+
Usage: task-info <taskId> OR info <taskId>
|
|
4
|
+
*/
|
|
4
5
|
const chalk = require('chalk');
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
name: 'task-info',
|
|
9
|
+
description: 'View full task details including test config and webhook URL',
|
|
10
|
+
aliases: ['task', 'info', 'taskInfo', '--t' ],
|
|
11
|
+
requiresAuth: true,
|
|
12
|
+
|
|
13
|
+
async execute(args, shell) {
|
|
14
|
+
if (!args[0]) {
|
|
15
|
+
console.log(chalk.red('Usage: task-info <task-id>'));
|
|
16
|
+
console.log(chalk.dim(' Alias: info <task-id>'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const taskId = args[0];
|
|
21
|
+
|
|
22
|
+
let task;
|
|
23
|
+
try {
|
|
24
|
+
task = await shell.api._request('GET', `/thinknsh/${taskId}/cli`);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err.message?.includes('404')) console.log(chalk.red('❌ Task not found'));
|
|
27
|
+
else if (err.message?.includes('403')) console.log(chalk.red('❌ Access denied'));
|
|
28
|
+
else console.log(chalk.red(`❌ ${err.message}`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const stColors = {
|
|
33
|
+
pending: chalk.yellow,
|
|
34
|
+
inprogress: chalk.blue,
|
|
35
|
+
review: chalk.magenta,
|
|
36
|
+
completed: chalk.green,
|
|
37
|
+
reopened: chalk.cyan,
|
|
38
|
+
};
|
|
39
|
+
const prColors = { high: chalk.red, medium: chalk.yellow, low: chalk.green };
|
|
40
|
+
const stColor = stColors[task.status] || chalk.white;
|
|
41
|
+
const prColor = prColors[task.priority] || chalk.white;
|
|
42
|
+
|
|
43
|
+
// ── Basic Info ────────────────────────────────────────────────────────────
|
|
44
|
+
console.log(chalk.cyan('\n ─── Task Info ───────────────────────────────────'));
|
|
45
|
+
console.log(` ${chalk.dim('id ')} ${chalk.dim(task._id)}${task.taskID ? chalk.cyan(' #' + task.taskID) : ''}`);
|
|
46
|
+
console.log(` ${chalk.dim('title ')} ${chalk.bold(task.title)}`);
|
|
47
|
+
console.log(` ${chalk.dim('status ')} ${stColor(task.status)}`);
|
|
48
|
+
console.log(` ${chalk.dim('priority')} ${prColor(task.priority)}`);
|
|
49
|
+
if (task.category)
|
|
50
|
+
console.log(` ${chalk.dim('category')} ${chalk.dim(task.category)}`);
|
|
51
|
+
if (task.dueDate)
|
|
52
|
+
console.log(` ${chalk.dim('due ')} ${chalk.yellow(new Date(task.dueDate).toLocaleDateString())}`);
|
|
53
|
+
if (task.description) {
|
|
54
|
+
console.log(chalk.dim('\n description:'));
|
|
55
|
+
task.description.split('\n').forEach(l => console.log(` ${chalk.white(l)}`));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Assignees ─────────────────────────────────────────────────────────────
|
|
59
|
+
if (task.assignedTo?.length) {
|
|
60
|
+
console.log(chalk.dim('\n assignees:'));
|
|
61
|
+
task.assignedTo.forEach(a => {
|
|
62
|
+
const name = a.userId?.name || 'Unknown';
|
|
63
|
+
const stBadge = a.status === 'completed' ? chalk.green('✓') :
|
|
64
|
+
a.status === 'accepted' ? chalk.blue('●') :
|
|
65
|
+
a.status === 'rejected' ? chalk.red('✗') : chalk.dim('○');
|
|
66
|
+
console.log(` ${stBadge} ${name} ${chalk.dim(`[${a.status}]`)}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Test Config ───────────────────────────────────────────────────────────
|
|
71
|
+
if (task.testConfig?.type) {
|
|
72
|
+
const tc = task.testConfig;
|
|
73
|
+
|
|
74
|
+
console.log(chalk.cyan('\n ─── Test Config ─────────────────────────────────'));
|
|
75
|
+
console.log(` ${chalk.dim('type ')} ${chalk.bold(tc.type)}`);
|
|
76
|
+
|
|
77
|
+
if (tc.type === 'http') {
|
|
78
|
+
console.log(` ${chalk.dim('method ')} ${chalk.green(tc.method || 'GET')}`);
|
|
79
|
+
console.log(` ${chalk.dim('target ')} ${chalk.white((tc.baseUrl || '') + (tc.endpoint || ''))}`);
|
|
80
|
+
if (tc.body && Object.keys(tc.body).length) {
|
|
81
|
+
console.log(` ${chalk.dim('body ')} ${chalk.dim(JSON.stringify(tc.body))}`);
|
|
82
|
+
}
|
|
83
|
+
if (tc.expect) {
|
|
84
|
+
console.log(chalk.dim('\n expect:'));
|
|
85
|
+
if (tc.expect.status !== undefined)
|
|
86
|
+
console.log(` ${chalk.dim('status')} ${chalk.green(tc.expect.status)}`);
|
|
87
|
+
if (tc.expect.body) {
|
|
88
|
+
Object.entries(tc.expect.body).forEach(([key, condition]) => {
|
|
89
|
+
const condStr = typeof condition === 'object'
|
|
90
|
+
? Object.entries(condition).map(([op, val]) => `${op}: ${val}`).join(', ')
|
|
91
|
+
: String(condition);
|
|
92
|
+
console.log(` ${chalk.dim('body.' + key)} ${chalk.cyan(condStr)}`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
16
95
|
}
|
|
96
|
+
}
|
|
17
97
|
|
|
98
|
+
if (tc.type === 'browser') {
|
|
99
|
+
console.log(` ${chalk.dim('url ')} ${chalk.white(tc.url || '')}`);
|
|
100
|
+
if (tc.flow?.length) {
|
|
101
|
+
console.log(chalk.dim('\n flow:'));
|
|
102
|
+
tc.flow.forEach((step, i) => {
|
|
103
|
+
console.log(` ${chalk.dim(i + 1 + '.')} ${chalk.green(step.action)} ${chalk.dim(step.selector || '')} ${step.value ? chalk.white(`"${step.value}"`) : ''}`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (tc.expect) {
|
|
107
|
+
console.log(chalk.dim('\n expect:'));
|
|
108
|
+
if (tc.expect.element)
|
|
109
|
+
console.log(` ${chalk.dim('element ')} ${chalk.cyan(tc.expect.element)}`);
|
|
110
|
+
if (tc.expect.text)
|
|
111
|
+
console.log(` ${chalk.dim('text ')} ${chalk.cyan(`contains "${tc.expect.text}"`)}`);
|
|
112
|
+
if (tc.expect.redirectUrl)
|
|
113
|
+
console.log(` ${chalk.dim('redirectUrl')} ${chalk.cyan(tc.expect.redirectUrl)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
18
116
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
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}`));
|
|
117
|
+
if (tc.type === 'json') {
|
|
118
|
+
if (tc.expect?.condition) {
|
|
119
|
+
console.log(chalk.dim('\n conditions:'));
|
|
120
|
+
Object.entries(tc.expect.condition).forEach(([key, ops]) => {
|
|
121
|
+
const condStr = Object.entries(ops).map(([op, val]) => `${op} ${val}`).join(', ');
|
|
122
|
+
console.log(` ${chalk.dim(key)} ${chalk.cyan(condStr)}`);
|
|
123
|
+
});
|
|
73
124
|
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (tc.type === 'exitCode')
|
|
128
|
+
console.log(` ${chalk.dim('expect ')} exitCode ${chalk.green(tc.expect?.exitCode ?? 0)}`);
|
|
129
|
+
|
|
130
|
+
if (tc.type === 'stdout')
|
|
131
|
+
console.log(` ${chalk.dim('expect ')} stdout "${chalk.green(tc.expect?.stdout || '')}"`);
|
|
132
|
+
|
|
133
|
+
if (tc.type === 'regex')
|
|
134
|
+
console.log(` ${chalk.dim('expect ')} regex ${chalk.green(tc.expect?.regex || '')}`);
|
|
135
|
+
|
|
136
|
+
} else {
|
|
137
|
+
console.log(chalk.yellow('\n ⚠️ No test config — task will need manual completion'));
|
|
138
|
+
console.log(chalk.dim(' Use: complete <task-id>'));
|
|
74
139
|
}
|
|
140
|
+
|
|
141
|
+
// ── Webhook ───────────────────────────────────────────────────────────────
|
|
142
|
+
if (task.webhookSecret) {
|
|
143
|
+
const apiUrl = shell.api.apiUrl || 'https://thinkncollab.com';
|
|
144
|
+
const webhookUrl = `${apiUrl}/webhooks/tasks/${taskId}/judge`;
|
|
145
|
+
|
|
146
|
+
console.log(chalk.cyan('\n ─── Webhook ─────────────────────────────────────'));
|
|
147
|
+
console.log(` ${chalk.dim('url ')} ${chalk.white(webhookUrl)}`);
|
|
148
|
+
console.log(` ${chalk.dim('secret ')} ${chalk.dim(task.webhookSecret)}`);
|
|
149
|
+
console.log(` ${chalk.dim('header ')} ${chalk.dim('x-tnc-signature: sha256=<HMAC of body>')}`);
|
|
150
|
+
console.log(chalk.dim('\n To submit from your project:'));
|
|
151
|
+
console.log(chalk.cyan(' submit ' + taskId));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Last Verdict ──────────────────────────────────────────────────────────
|
|
155
|
+
if (task.lastVerdict?.at) {
|
|
156
|
+
const v = task.lastVerdict;
|
|
157
|
+
const passed = v.passed;
|
|
158
|
+
const badge = passed ? chalk.bgGreen.black(' PASS ') : chalk.bgRed.white(' FAIL ');
|
|
159
|
+
const ts = new Date(v.at).toLocaleString();
|
|
160
|
+
|
|
161
|
+
console.log(chalk.cyan('\n ─── Last Verdict ────────────────────────────────'));
|
|
162
|
+
console.log(` ${badge} ${passed ? chalk.green(v.reason) : chalk.red(v.reason)}`);
|
|
163
|
+
console.log(` ${chalk.dim('at')} ${chalk.dim(ts)}`);
|
|
164
|
+
if (v.diff) {
|
|
165
|
+
console.log(` ${chalk.dim('expected')} ${chalk.cyan(JSON.stringify(v.diff.expected ?? v.diff.conditions))}`);
|
|
166
|
+
console.log(` ${chalk.dim('actual ')} ${chalk.yellow(JSON.stringify(v.diff.actual))}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(chalk.cyan(' ─────────────────────────────────────────────────\n'));
|
|
171
|
+
}
|
|
75
172
|
};
|
|
@@ -106,9 +106,10 @@ module.exports = {
|
|
|
106
106
|
const col = task.column ? chalk.dim(` [${task.column}]`) : '';
|
|
107
107
|
|
|
108
108
|
console.log(`\n${num} ${status} ${title}`);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
|
|
110
|
+
console.log(subline('id', `${task.taskID ? '#' + task.taskID : task._id}`) +
|
|
111
|
+
` ${chalk.dim('priority:')} ${pc(task.priority)}` +
|
|
112
|
+
due + myStatus + col);
|
|
112
113
|
|
|
113
114
|
if (task.description) {
|
|
114
115
|
const desc = task.description.length > 90
|
package/src/core/api-client.js
CHANGED
|
@@ -148,7 +148,7 @@ async logEvent(type, roomId = null, meta = {}) {
|
|
|
148
148
|
if (!sessionId) return;
|
|
149
149
|
await this._request('POST', '/thinknsh/session/event', {
|
|
150
150
|
sessionId, type, roomId, meta
|
|
151
|
-
}).catch(() => {});
|
|
151
|
+
}).catch(() => {});
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
// ─── Heartbeat — keeps lastSeenAt fresh while shell is open ──────────────
|