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.
@@ -1,75 +1,172 @@
1
1
  /*
2
- src/commands/tasks/task.js — View single task detail
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
- 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;
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
- const taskId = args[0];
21
- try {
22
- const task = await shell.api._request('GET', `/thinknsh/${taskId}/cli`);
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}`));
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
- console.log(subline('id', task._id) +
110
- ` ${chalk.dim('priority:')} ${pc(task.priority)}` +
111
- due + myStatus + col);
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
@@ -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(() => {}); // silently fail — never block shell
151
+ }).catch(() => {});
152
152
  }
153
153
 
154
154
  // ─── Heartbeat — keeps lastSeenAt fresh while shell is open ──────────────