thinkncollab-shell 0.0.12 → 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.
package/Readme.md CHANGED
@@ -21,6 +21,7 @@ A real-time collaborative terminal shell with WebSocket integration, built on to
21
21
  - [Terminal Share Commands](#terminal-share-commands)
22
22
  - [Notification Commands](#notification-commands)
23
23
  - [Shell Commands](#shell-commands)
24
+ - [GitHub Integration Commands](#github-integration-commands)
24
25
  - [`.tncproject` Auto-Boot](#tncproject-auto-boot)
25
26
  - [Notification Window](#notification-window)
26
27
  - [Built-in Shell Features](#built-in-shell-features)
@@ -411,6 +412,37 @@ Exit the shell gracefully (saves history, closes TCP notification server, discon
411
412
 
412
413
  ---
413
414
 
415
+ ### GitHub Integration Commands
416
+
417
+ These commands let you interact with the GitHub repository linked to your current room. Requires connecting your GitHub account and repository on the Web Dashboard first.
418
+
419
+ #### `statusgh`
420
+ Show GitHub connection status and linked repository details.
421
+ * **Aliases:** `statusgt`, `gh-status`
422
+ * **Example:** `statusgh`
423
+
424
+ #### `issues`
425
+ List open issues in the linked GitHub repository.
426
+ * **Aliases:** `gh-issues`
427
+ * **Example:** `issues`
428
+
429
+ #### `pulls`
430
+ List open pull requests in the linked GitHub repository.
431
+ * **Aliases:** `prs`, `gh-pulls`, `gh-prs`
432
+ * **Example:** `pulls`
433
+
434
+ #### `commits`
435
+ List the last 15 commits in the linked GitHub repository.
436
+ * **Aliases:** `gh-commits`
437
+ * **Example:** `commits`
438
+
439
+ #### `issue-create <title> <body>`
440
+ Create a new issue in the linked GitHub repository. If arguments are missing, the shell prompts for them interactively.
441
+ * **Aliases:** `create-issue`, `gh-issue-create`, `gh-create-issue`
442
+ * **Example:** `issue-create "API Timeout" "GET /thinknsh is failing under load"`
443
+
444
+ ---
445
+
414
446
  ## `.tncproject` Auto-Boot
415
447
 
416
448
  Place a `.tncproject` file in your project root to auto-login and auto-join a room when `thinknsh` starts in that directory — no manual `login` or `join` needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkncollab-shell",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "ThinkNCollab Custom Shell with WebSocket collaboration",
5
5
  "main": "src/core/shell.js",
6
6
  "bin": {
@@ -0,0 +1,58 @@
1
+ /*
2
+ GitHub commits Command
3
+ */
4
+ const chalk = require('chalk');
5
+ const Table = require('cli-table3');
6
+
7
+ module.exports = {
8
+ name: 'commits',
9
+ description: 'List recent GitHub commits for the linked repository',
10
+ aliases: ['gh-commits'],
11
+ requiresAuth: true,
12
+ requiresRoom: true,
13
+
14
+ async execute(args, shell) {
15
+ const roomId = shell.ws.getCurrentRoom();
16
+ if (!roomId) {
17
+ console.log(chalk.yellow('⚠️ Please join a room first: join <room-id>'));
18
+ return;
19
+ }
20
+
21
+ console.log(chalk.dim('\n Fetching recent commits...\r'));
22
+ try {
23
+ const commits = await shell.api._request('GET', `/thinknsh/${roomId}/github/commits`);
24
+ console.log(' '.repeat(30) + '\r'); // clear line
25
+
26
+ console.log(chalk.cyan(`\n🐙 Recent Commits (Last 15):`));
27
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
28
+
29
+ if (!commits || commits.length === 0) {
30
+ console.log(chalk.dim(' No commits found.'));
31
+ console.log();
32
+ return;
33
+ }
34
+
35
+ const table = new Table({
36
+ head: ['SHA', 'Message', 'Author', 'Date'],
37
+ colWidths: [10, 45, 18, 15]
38
+ });
39
+
40
+ commits.forEach(commit => {
41
+ const shortSha = commit.sha.slice(0, 7);
42
+ const shortMsg = commit.message.split('\n')[0];
43
+ table.push([
44
+ chalk.yellow(shortSha),
45
+ chalk.white(shortMsg.length > 40 ? shortMsg.slice(0, 37) + '...' : shortMsg),
46
+ commit.author || '-',
47
+ new Date(commit.date).toLocaleDateString()
48
+ ]);
49
+ });
50
+
51
+ console.log(table.toString());
52
+ console.log();
53
+ } catch (error) {
54
+ console.log(' '.repeat(30) + '\r');
55
+ console.log(chalk.red(`❌ Failed to fetch commits: ${error.message}`));
56
+ }
57
+ }
58
+ };
@@ -0,0 +1,67 @@
1
+ /*
2
+ GitHub issue-create Command
3
+ */
4
+ const chalk = require('chalk');
5
+
6
+ module.exports = {
7
+ name: 'issue-create',
8
+ description: 'Create a new GitHub issue in the linked repository',
9
+ aliases: ['create-issue', 'gh-issue-create', 'gh-create-issue'],
10
+ requiresAuth: true,
11
+ requiresRoom: true,
12
+
13
+ async execute(args, shell) {
14
+ const roomId = shell.ws.getCurrentRoom();
15
+ if (!roomId) {
16
+ console.log(chalk.yellow('⚠️ Please join a room first: join <room-id>'));
17
+ return;
18
+ }
19
+
20
+ let title = args[0];
21
+ let body = args.slice(1).join(' ');
22
+
23
+ // If title/body not provided via command line, prompt interactively
24
+ if (!title) {
25
+ try {
26
+ const inquirer = require('inquirer');
27
+ const answers = await inquirer.prompt([
28
+ {
29
+ type: 'input',
30
+ name: 'title',
31
+ message: 'Enter issue title:',
32
+ validate: input => input.trim() ? true : 'Title is required'
33
+ },
34
+ {
35
+ type: 'input',
36
+ name: 'body',
37
+ message: 'Enter issue description (body) (optional):'
38
+ }
39
+ ]);
40
+ title = answers.title;
41
+ body = answers.body;
42
+ } catch (err) {
43
+ console.log(chalk.red('❌ Missing arguments. Usage: issue-create <title> <body>'));
44
+ return;
45
+ }
46
+ }
47
+
48
+ console.log(chalk.dim('\n Creating GitHub issue...\r'));
49
+ try {
50
+ const result = await shell.api._request('POST', `/thinknsh/${roomId}/github/issues`, {
51
+ title,
52
+ body
53
+ });
54
+ console.log(' '.repeat(30) + '\r'); // clear line
55
+
56
+ console.log(chalk.green(`\n✅ GitHub issue created successfully!`));
57
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
58
+ console.log(` ${chalk.yellow('Issue Number:')} #${result.number}`);
59
+ console.log(` ${chalk.yellow('Title:')} ${result.title}`);
60
+ console.log(` ${chalk.yellow('URL:')} ${chalk.cyan(result.html_url)}`);
61
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
62
+ } catch (error) {
63
+ console.log(' '.repeat(30) + '\r');
64
+ console.log(chalk.red(`❌ Failed to create issue: ${error.message}`));
65
+ }
66
+ }
67
+ };
@@ -0,0 +1,57 @@
1
+ /*
2
+ GitHub issues Command
3
+ */
4
+ const chalk = require('chalk');
5
+ const Table = require('cli-table3');
6
+
7
+ module.exports = {
8
+ name: 'issues',
9
+ description: 'List open GitHub issues for the linked repository',
10
+ aliases: ['gh-issues'],
11
+ requiresAuth: true,
12
+ requiresRoom: true,
13
+
14
+ async execute(args, shell) {
15
+ const roomId = shell.ws.getCurrentRoom();
16
+ if (!roomId) {
17
+ console.log(chalk.yellow('⚠️ Please join a room first: join <room-id>'));
18
+ return;
19
+ }
20
+
21
+ console.log(chalk.dim('\n Fetching open issues...\r'));
22
+ try {
23
+ const issues = await shell.api._request('GET', `/thinknsh/${roomId}/github/issues`);
24
+ console.log(' '.repeat(30) + '\r'); // clear line
25
+
26
+ console.log(chalk.cyan(`\n🐙 Open GitHub Issues:`));
27
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
28
+
29
+ if (!issues || issues.length === 0) {
30
+ console.log(chalk.dim(' No open issues found in the linked repository.'));
31
+ console.log();
32
+ return;
33
+ }
34
+
35
+ const table = new Table({
36
+ head: ['#', 'Title', 'Author', 'Comments', 'Updated At'],
37
+ colWidths: [8, 45, 15, 10, 15]
38
+ });
39
+
40
+ issues.forEach(issue => {
41
+ table.push([
42
+ chalk.green(`#${issue.number}`),
43
+ chalk.white(issue.title.length > 40 ? issue.title.slice(0, 37) + '...' : issue.title),
44
+ issue.user || '-',
45
+ issue.comments,
46
+ new Date(issue.updated_at).toLocaleDateString()
47
+ ]);
48
+ });
49
+
50
+ console.log(table.toString());
51
+ console.log();
52
+ } catch (error) {
53
+ console.log(' '.repeat(30) + '\r');
54
+ console.log(chalk.red(`❌ Failed to fetch issues: ${error.message}`));
55
+ }
56
+ }
57
+ };
@@ -0,0 +1,58 @@
1
+ /*
2
+ GitHub pulls Command
3
+ */
4
+ const chalk = require('chalk');
5
+ const Table = require('cli-table3');
6
+
7
+ module.exports = {
8
+ name: 'pulls',
9
+ description: 'List open GitHub pull requests for the linked repository',
10
+ aliases: ['prs', 'gh-pulls', 'gh-prs'],
11
+ requiresAuth: true,
12
+ requiresRoom: true,
13
+
14
+ async execute(args, shell) {
15
+ const roomId = shell.ws.getCurrentRoom();
16
+ if (!roomId) {
17
+ console.log(chalk.yellow('⚠️ Please join a room first: join <room-id>'));
18
+ return;
19
+ }
20
+
21
+ console.log(chalk.dim('\n Fetching open pull requests...\r'));
22
+ try {
23
+ const pulls = await shell.api._request('GET', `/thinknsh/${roomId}/github/pulls`);
24
+ console.log(' '.repeat(30) + '\r'); // clear line
25
+
26
+ console.log(chalk.cyan(`\n🐙 Open GitHub Pull Requests:`));
27
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
28
+
29
+ if (!pulls || pulls.length === 0) {
30
+ console.log(chalk.dim(' No open pull requests found.'));
31
+ console.log();
32
+ return;
33
+ }
34
+
35
+ const table = new Table({
36
+ head: ['#', 'Title', 'Author', 'Status', 'Updated At'],
37
+ colWidths: [8, 45, 15, 10, 15]
38
+ });
39
+
40
+ pulls.forEach(pr => {
41
+ const statusStr = pr.draft ? chalk.gray('Draft') : chalk.green('Open');
42
+ table.push([
43
+ chalk.green(`#${pr.number}`),
44
+ chalk.white(pr.title.length > 40 ? pr.title.slice(0, 37) + '...' : pr.title),
45
+ pr.user || '-',
46
+ statusStr,
47
+ new Date(pr.updated_at).toLocaleDateString()
48
+ ]);
49
+ });
50
+
51
+ console.log(table.toString());
52
+ console.log();
53
+ } catch (error) {
54
+ console.log(' '.repeat(30) + '\r');
55
+ console.log(chalk.red(`❌ Failed to fetch pull requests: ${error.message}`));
56
+ }
57
+ }
58
+ };
@@ -0,0 +1,50 @@
1
+ /*
2
+ GitHub statusgh Command
3
+ */
4
+ const chalk = require('chalk');
5
+
6
+ module.exports = {
7
+ name: 'statusgh',
8
+ description: 'Show GitHub connection and linked repository status',
9
+ aliases: ['statusgt', 'gh-status'],
10
+ requiresAuth: true,
11
+ requiresRoom: true,
12
+
13
+ async execute(args, shell) {
14
+ const roomId = shell.ws.getCurrentRoom();
15
+ if (!roomId) {
16
+ console.log(chalk.yellow('⚠️ Please join a room first: join <room-id>'));
17
+ return;
18
+ }
19
+
20
+ console.log(chalk.dim('\n Fetching GitHub status...\r'));
21
+ try {
22
+ const status = await shell.api._request('GET', `/thinknsh/${roomId}/github/status`);
23
+ console.log(' '.repeat(30) + '\r'); // clear line
24
+
25
+ console.log(chalk.cyan(`\n🐙 GitHub Integration Status:`));
26
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
27
+
28
+ console.log(` ${chalk.yellow('GitHub Connection:')} ${status.connected ? chalk.green('Connected ✓') : chalk.red('Not Connected ✗')}`);
29
+ if (status.githubUsername) {
30
+ console.log(` ${chalk.yellow('GitHub Username:')} ${status.githubUsername}`);
31
+ }
32
+
33
+ console.log(` ${chalk.yellow('Repository Linked:')} ${status.repoLinked ? chalk.green('Linked ✓') : chalk.red('Not Linked ✗')}`);
34
+ if (status.repo) {
35
+ console.log(` ${chalk.yellow('Repository Name:')} ${status.repo.fullName || `${status.repo.owner}/${status.repo.name}`}`);
36
+ }
37
+
38
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
39
+ if (!status.connected) {
40
+ console.log(chalk.yellow('\n💡 Please connect your GitHub account via the Web Dashboard integration panel.'));
41
+ } else if (!status.repoLinked) {
42
+ console.log(chalk.yellow('\n💡 Please link a GitHub repository to this room on the Web Dashboard repository setting page.'));
43
+ }
44
+ console.log();
45
+ } catch (error) {
46
+ console.log(' '.repeat(30) + '\r');
47
+ console.log(chalk.red(`❌ Failed to fetch GitHub status: ${error.message}`));
48
+ }
49
+ }
50
+ };
@@ -56,14 +56,18 @@ const load = (path, category) => {
56
56
  load('./tasks/complete', 'tasks');
57
57
  // load('./tasks/comment', 'tasks');
58
58
  load('./tasks/create-task', 'tasks');
59
+ load('./tasks/loadtest', 'tasks');
59
60
 
60
61
 
61
62
  // Terminal
62
63
  load('./terminal/share', 'terminal');
63
64
 
64
-
65
-
66
-
65
+ // GitHub
66
+ load('./github/statusgh', 'github');
67
+ load('./github/issues', 'github');
68
+ load('./github/pulls', 'github');
69
+ load('./github/commits', 'github');
70
+ load('./github/issue-create', 'github');
67
71
 
68
72
  }
69
73
 
@@ -85,31 +89,31 @@ const load = (path, category) => {
85
89
  return this.commands.get(name);
86
90
  }
87
91
 
88
- async execute(input, shell) {
89
- const args = shell.parseCommand(input);
90
- const cmdName = args[0].toLowerCase();
91
- const cmdArgs = args.slice(1);
92
-
93
- const command = this.getCommand(cmdName);
94
- if (!command) return false;
95
-
96
- try {
97
- if (command.requiresAuth && !shell.api?.isAuthenticated()) {
98
- console.log(chalk.red('❌ Please login first: login'));
99
- return true;
100
- }
101
- if (command.requiresRoom && !shell.ws?.getCurrentRoom()) {
102
- console.log(chalk.red('❌ Please join a room first: join <room-id>'));
103
- return true;
104
- }
105
- await command.execute(cmdArgs, shell);
106
- } catch (error) {
107
- console.log(chalk.red(`❌ Command error: ${error.message}`));
108
- }
109
-
110
- return true;
92
+ async execute(input, shell) {
93
+ const args = shell.parseCommand(input);
94
+ const cmdName = args[0].toLowerCase();
95
+ const cmdArgs = args.slice(1);
96
+ const command = this.getCommand(cmdName);
97
+ if (!command) return false;
98
+
99
+ try {
100
+ if (command.requiresAuth && !shell.api?.isAuthenticated()) {
101
+ console.log(chalk.red('❌ Please login first: login'));
102
+ return true;
103
+ }
104
+ if (command.requiresRoom && !shell.ws?.getCurrentRoom()) {
105
+ console.log(chalk.red('❌ Please join a room first: join <room-id>'));
106
+ return true;
111
107
  }
112
108
 
109
+ shell._lastCommand = cmdName; // ← backward compat
110
+ await command.execute(cmdArgs, shell, cmdName); // ← invokedAs as 3rd arg
111
+ } catch (error) {
112
+ console.log(chalk.red(`❌ Command error: ${error.message}`));
113
+ }
114
+ return true;
115
+ }
116
+
113
117
  getHelpText() {
114
118
  let help = chalk.cyan('\n📚 Available Commands:\n');
115
119
  help += chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n');
@@ -14,20 +14,11 @@ module.exports = {
14
14
  requiresRoom: true,
15
15
 
16
16
  async execute(args, shell) {
17
- const room = shell.api.getCurrentRoom();
18
-
19
- if (!room) {
20
- console.log(chalk.yellow('⚠️ Not in any room'));
21
- return;
22
- }
23
-
24
- console.log(chalk.cyan(`👋 Leaving room: ${room.name}...`));
25
-
26
- try {
27
- await shell.api.leaveRoom();
28
- console.log(chalk.green(`✅ Left room: ${room.name}`));
29
- } catch (error) {
30
- console.log(chalk.red(`❌ Failed to leave: ${error.message}`));
31
- }
17
+ const room = shell.ws.getCurrentRoom();
18
+ if (!room) { console.log(chalk.yellow('⚠️ Not in any room')); return; }
19
+ console.log(chalk.cyan(`👋 Leaving room...`));
20
+ await shell.ws.leaveRoom();
21
+ console.log(chalk.green(`✅ Left room`));
22
+
32
23
  }
33
24
  };
@@ -14,9 +14,9 @@ module.exports = {
14
14
  requiresAuth: false,
15
15
 
16
16
  async execute(args, shell) {
17
- const user = shell.api.getUser();
18
- const room = shell.api.getCurrentRoom();
19
- const connected = shell.api.isConnected();
17
+ const user = shell.ws.getUser();
18
+ const room = shell.ws.getCurrentRoom();
19
+ const connected = shell.ws.isConnected();
20
20
 
21
21
  console.log(chalk.cyan('\n📊 ThinkNCollab Shell Status'));
22
22
  console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
@@ -25,8 +25,8 @@ module.exports = {
25
25
  console.log(` ${chalk.yellow('Connection:')} ${connected ? chalk.green('● Online') : chalk.red('○ Offline')}`);
26
26
 
27
27
  if (connected) {
28
- console.log(` ${chalk.yellow('Server:')} ${shell.api.config.wsUrl}`);
29
- console.log(` ${chalk.yellow('Socket ID:')} ${shell.api.getSocketId() || 'N/A'}`);
28
+ console.log(` ${chalk.yellow('Server:')} ${shell.ws.config.wsUrl}`);
29
+ console.log(` ${chalk.yellow('Socket ID:')} ${shell.ws.getSocketId() || 'N/A'}`);
30
30
  }
31
31
 
32
32
  // Authentication