thinkncollab-shell 0.0.10 → 0.0.13

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.
@@ -16,7 +16,7 @@ module.exports = {
16
16
 
17
17
  try {
18
18
  // Fetch columns of current room's board
19
- const { columns } = await shell.api._request('GET', `/rooms/${roomId}/cli/columns`);
19
+ const { columns } = await shell.api._request('GET', `/thinknsh/${roomId}/cli/columns`);
20
20
  if (!columns?.length) {
21
21
  console.log(chalk.red('❌ No columns found in this room\'s board'));
22
22
  return;
@@ -78,7 +78,7 @@ module.exports = {
78
78
  source: 'cli',
79
79
  };
80
80
 
81
- const result = await shell.api._request('POST', `/${roomId}/cli/tasks`, payload);
81
+ const result = await shell.api._request('POST', `/thinknsh/${roomId}/cli/tasks`, payload);
82
82
 
83
83
  console.log(chalk.green(`\n✅ Task created: "${result.task.title}"`));
84
84
  console.log(chalk.dim(` ID: ${result.task._id}`));
@@ -0,0 +1,73 @@
1
+ const chalk = require('chalk');
2
+ const path = require('path');
3
+ const fs = require('fs-extra');
4
+ const { runLoadTest } = require('../../core/geekload');
5
+ const Table = require('cli-table3');
6
+
7
+ module.exports = {
8
+ name: 'loadtest',
9
+ description: 'Run a local load test script using GeekLoad',
10
+ aliases: ['geekload'],
11
+ requiresAuth: false,
12
+
13
+ async execute(args, shell) {
14
+ if (!args[0]) {
15
+ console.log(chalk.red('Usage: loadtest <script-path>'));
16
+ return;
17
+ }
18
+
19
+ const scriptPath = path.resolve(process.cwd(), args[0]);
20
+ if (!fs.existsSync(scriptPath)) {
21
+ console.log(chalk.red(`❌ Script not found: ${args[0]}`));
22
+ return;
23
+ }
24
+
25
+ console.log(chalk.cyan(`\n📋 Loading test script: ${args[0]}`));
26
+
27
+ try {
28
+ const results = await runLoadTest(scriptPath);
29
+
30
+ const table = new Table({
31
+ head: [chalk.green('Metric'), chalk.green('Value')],
32
+ colWidths: [25, 20]
33
+ });
34
+
35
+ table.push(
36
+ ['Virtual Users', results.virtual_users],
37
+ ['Duration (sec)', results.duration_sec],
38
+ ['Total Requests', results.total_requests],
39
+ ['Failed Requests', results.failed_requests],
40
+ ['Requests/sec (RPS)', results.requests_per_sec],
41
+ ['Failure Rate', `${(results.failure_rate * 100).toFixed(2)}%`],
42
+ ['Average Latency (ms)', results.avg_latency_ms],
43
+ ['p95 Latency (ms)', results.p95_latency_ms],
44
+ ['p99 Latency (ms)', results.p99_latency_ms]
45
+ );
46
+
47
+ console.log(table.toString());
48
+
49
+ if (results.failures.length > 0) {
50
+ console.log(chalk.yellow(`\n⚠️ Recorded Failures (${results.failures.length}):`));
51
+ results.failures.slice(0, 5).forEach((f, idx) => {
52
+ console.log(chalk.dim(` ${idx + 1}. [${f.method}] ${f.url}`));
53
+ f.failures.forEach(msg => {
54
+ console.log(chalk.red(` - ${msg}`));
55
+ });
56
+ });
57
+ if (results.failures.length > 5) {
58
+ console.log(chalk.dim(` ... and ${results.failures.length - 5} more failures`));
59
+ }
60
+ }
61
+
62
+ if (results.errors.length > 0) {
63
+ console.log(chalk.red(`\n❌ Execution Errors (${results.errors.length}):`));
64
+ results.errors.slice(0, 5).forEach((e, idx) => {
65
+ console.log(chalk.red(` VU ${e.vuId} Error: ${e.message}`));
66
+ });
67
+ }
68
+
69
+ } catch (err) {
70
+ console.log(chalk.red(`❌ Load test execution failed: ${err.message}`));
71
+ }
72
+ }
73
+ };
@@ -2,60 +2,71 @@
2
2
  const chalk = require('chalk');
3
3
 
4
4
  const STATUS_MAP = {
5
- accept: 'accepted',
6
- start: 'inprogress',
7
- complete: 'completed',
8
- reject: 'rejected',
9
- done: 'completed',
5
+ accept: 'accepted',
6
+ start: 'inprogress',
7
+ complete: 'completed',
8
+ reject: 'rejected',
9
+ done: 'completed',
10
+ 'task-status': null, // requires explicit arg
10
11
  };
11
12
 
12
13
  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
- }
14
+ name: 'task-status',
15
+ description: 'Update your task status',
16
+ aliases: ['accept', 'start', 'complete', 'reject', 'done'],
17
+ requiresAuth: true,
18
+ requiresRoom: true,
19
+
20
+ async execute(args, shell, invokedAs) {
21
+ const roomId = shell.ws.getCurrentRoom(); // ← top pe, ek baar
22
+ const taskId = args[0];
23
+ const verb = invokedAs || 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
+ try {
36
+ const result = await shell.api._request(
37
+ 'POST',
38
+ `/thinknsh/${roomId}/tasks/${taskId}/status`,
39
+ { status }
40
+ );
41
+
42
+ const statusColors = {
43
+ accepted: chalk.green,
44
+ inprogress: chalk.blue,
45
+ completed: chalk.green,
46
+ rejected: chalk.red,
47
+ };
48
+
49
+ const col = statusColors[status] || chalk.white;
50
+ console.log(col(`\n ✓ Task "${result.taskTitle}" → ${status}`));
51
+
52
+ if (result.allCompleted) {
53
+ console.log(chalk.green(' 🎉 All assignees done — task fully completed!\n'));
54
+ } else {
55
+ console.log('');
56
+ }
57
+
58
+ // Socket emit
59
+ if (shell.ws.isConnected?.() && shell.ws.socket) {
60
+ shell.ws.socket.emit('task:' + (status === 'inprogress' ? 'started' : status), {
61
+ roomId,
62
+ taskId,
63
+ taskTitle: result.taskTitle,
64
+ userId: shell.api.getUser()?._id,
65
+ });
60
66
  }
67
+
68
+ } catch (err) {
69
+ console.log(chalk.red(`\n ❌ ${err.message}\n`));
70
+ }
71
+ }
61
72
  };
@@ -1,75 +1,179 @@
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> load
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
+ if (task.referenceLinks && task.referenceLinks.length > 0) {
59
+ console.log(chalk.cyan('\n ─── Supported Links ─────────────────────────────'));
60
+ task.referenceLinks.forEach(link => {
61
+ console.log(` ${chalk.dim(link.title.padEnd(12))} ${chalk.blue(link.url)}`);
62
+ });
63
+ }
64
+
65
+ // ── Assignees ─────────────────────────────────────────────────────────────
66
+ if (task.assignedTo?.length) {
67
+ console.log(chalk.dim('\n assignees:'));
68
+ task.assignedTo.forEach(a => {
69
+ const name = a.userId?.name || 'Unknown';
70
+ const stBadge = a.status === 'completed' ? chalk.green('✓') :
71
+ a.status === 'accepted' ? chalk.blue('●') :
72
+ a.status === 'rejected' ? chalk.red('✗') : chalk.dim('○');
73
+ console.log(` ${stBadge} ${name} ${chalk.dim(`[${a.status}]`)}`);
74
+ });
75
+ }
76
+
77
+ // ── Test Config ───────────────────────────────────────────────────────────
78
+ if (task.testConfig?.type) {
79
+ const tc = task.testConfig;
80
+
81
+ console.log(chalk.cyan('\n ─── Test Config ─────────────────────────────────'));
82
+ console.log(` ${chalk.dim('type ')} ${chalk.bold(tc.type)}`);
83
+
84
+ if (tc.type === 'http') {
85
+ console.log(` ${chalk.dim('method ')} ${chalk.green(tc.method || 'GET')}`);
86
+ console.log(` ${chalk.dim('target ')} ${chalk.white((tc.baseUrl || '') + (tc.endpoint || ''))}`);
87
+ if (tc.body && Object.keys(tc.body).length) {
88
+ console.log(` ${chalk.dim('body ')} ${chalk.dim(JSON.stringify(tc.body))}`);
16
89
  }
90
+ if (tc.expect) {
91
+ console.log(chalk.dim('\n expect:'));
92
+ if (tc.expect.status !== undefined)
93
+ console.log(` ${chalk.dim('status')} ${chalk.green(tc.expect.status)}`);
94
+ if (tc.expect.body) {
95
+ Object.entries(tc.expect.body).forEach(([key, condition]) => {
96
+ const condStr = typeof condition === 'object'
97
+ ? Object.entries(condition).map(([op, val]) => `${op}: ${val}`).join(', ')
98
+ : String(condition);
99
+ console.log(` ${chalk.dim('body.' + key)} ${chalk.cyan(condStr)}`);
100
+ });
101
+ }
102
+ }
103
+ }
17
104
 
105
+ if (tc.type === 'browser') {
106
+ console.log(` ${chalk.dim('url ')} ${chalk.white(tc.url || '')}`);
107
+ if (tc.flow?.length) {
108
+ console.log(chalk.dim('\n flow:'));
109
+ tc.flow.forEach((step, i) => {
110
+ console.log(` ${chalk.dim(i + 1 + '.')} ${chalk.green(step.action)} ${chalk.dim(step.selector || '')} ${step.value ? chalk.white(`"${step.value}"`) : ''}`);
111
+ });
112
+ }
113
+ if (tc.expect) {
114
+ console.log(chalk.dim('\n expect:'));
115
+ if (tc.expect.element)
116
+ console.log(` ${chalk.dim('element ')} ${chalk.cyan(tc.expect.element)}`);
117
+ if (tc.expect.text)
118
+ console.log(` ${chalk.dim('text ')} ${chalk.cyan(`contains "${tc.expect.text}"`)}`);
119
+ if (tc.expect.redirectUrl)
120
+ console.log(` ${chalk.dim('redirectUrl')} ${chalk.cyan(tc.expect.redirectUrl)}`);
121
+ }
122
+ }
18
123
 
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}`));
124
+ if (tc.type === 'json') {
125
+ if (tc.expect?.condition) {
126
+ console.log(chalk.dim('\n conditions:'));
127
+ Object.entries(tc.expect.condition).forEach(([key, ops]) => {
128
+ const condStr = Object.entries(ops).map(([op, val]) => `${op} ${val}`).join(', ');
129
+ console.log(` ${chalk.dim(key)} ${chalk.cyan(condStr)}`);
130
+ });
73
131
  }
132
+ }
133
+
134
+ if (tc.type === 'exitCode')
135
+ console.log(` ${chalk.dim('expect ')} exitCode ${chalk.green(tc.expect?.exitCode ?? 0)}`);
136
+
137
+ if (tc.type === 'stdout')
138
+ console.log(` ${chalk.dim('expect ')} stdout "${chalk.green(tc.expect?.stdout || '')}"`);
139
+
140
+ if (tc.type === 'regex')
141
+ console.log(` ${chalk.dim('expect ')} regex ${chalk.green(tc.expect?.regex || '')}`);
142
+
143
+ } else {
144
+ console.log(chalk.yellow('\n ⚠️ No test config — task will need manual completion'));
145
+ console.log(chalk.dim(' Use: complete <task-id>'));
74
146
  }
147
+
148
+ // ── Webhook ───────────────────────────────────────────────────────────────
149
+ if (task.webhookSecret) {
150
+ const apiUrl = shell.api.apiUrl || 'https://thinkncollab.com';
151
+ const webhookUrl = `${apiUrl}/webhooks/tasks/${taskId}/judge`;
152
+
153
+ console.log(chalk.cyan('\n ─── Webhook ─────────────────────────────────────'));
154
+ console.log(` ${chalk.dim('url ')} ${chalk.white(webhookUrl)}`);
155
+ console.log(` ${chalk.dim('secret ')} ${chalk.dim(task.webhookSecret)}`);
156
+ console.log(` ${chalk.dim('header ')} ${chalk.dim('x-tnc-signature: sha256=<HMAC of body>')}`);
157
+ console.log(chalk.dim('\n To submit from your project:'));
158
+ console.log(chalk.cyan(' submit ' + taskId));
159
+ }
160
+
161
+ // ── Last Verdict ──────────────────────────────────────────────────────────
162
+ if (task.lastVerdict?.at) {
163
+ const v = task.lastVerdict;
164
+ const passed = v.passed;
165
+ const badge = passed ? chalk.bgGreen.black(' PASS ') : chalk.bgRed.white(' FAIL ');
166
+ const ts = new Date(v.at).toLocaleString();
167
+
168
+ console.log(chalk.cyan('\n ─── Last Verdict ────────────────────────────────'));
169
+ console.log(` ${badge} ${passed ? chalk.green(v.reason) : chalk.red(v.reason)}`);
170
+ console.log(` ${chalk.dim('at')} ${chalk.dim(ts)}`);
171
+ if (v.diff) {
172
+ console.log(` ${chalk.dim('expected')} ${chalk.cyan(JSON.stringify(v.diff.expected ?? v.diff.conditions))}`);
173
+ console.log(` ${chalk.dim('actual ')} ${chalk.yellow(JSON.stringify(v.diff.actual))}`);
174
+ }
175
+ }
176
+
177
+ console.log(chalk.cyan(' ─────────────────────────────────────────────────\n'));
178
+ }
75
179
  };
@@ -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
@@ -63,7 +63,7 @@ async function cmdStart(shell) {
63
63
  console.log(chalk.green(`\n✅ Terminal sharing started`));
64
64
  console.log(chalk.dim(` Session : ${data.sessionId}`));
65
65
  console.log(chalk.dim(` Room : ${roomId}`));
66
- console.log(chalk.dim(` URL : /terminal/${data.sessionId}`));
66
+ console.log(chalk.dim(` URL : https://thinkncollab.com/terminal/${data.sessionId}`));
67
67
  console.log(chalk.dim(`\n Room members can now view your terminal.`));
68
68
  console.log(chalk.dim(` Use "share grant <userId>" to give write access.\n`));
69
69
 
@@ -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 ──────────────