thinkncollab-shell 0.0.12 → 0.0.14

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,7 +21,8 @@ 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
- - [`.tncproject` Auto-Boot](#tncproject-auto-boot)
24
+ - [GitHub Integration Commands](#github-integration-commands)
25
+ - [`tncproject` Auto-Boot](#tncproject-auto-boot)
25
26
  - [Notification Window](#notification-window)
26
27
  - [Built-in Shell Features](#built-in-shell-features)
27
28
  - [Single-Command Mode](#single-command-mode)
@@ -37,7 +38,7 @@ A real-time collaborative terminal shell with WebSocket integration, built on to
37
38
  - šŸ“‹ **Task Management** — Accept, start, complete, and reject tasks from the CLI
38
39
  - šŸ“ŗ **Terminal Sharing** — Share your live terminal session with teammates (read + write access)
39
40
  - šŸ”” **Notification Window** — Background TCP-based popup window for live events
40
- - šŸ”‘ **`.tncproject` Auto-Boot** — Drop a config file in your project root to auto-login and join a room on shell start
41
+ - šŸ”‘ **`tncproject` Auto-Boot** — Drop a config file in your project root to auto-login and join a room on shell start
41
42
  - šŸ›”ļø **Shell Token Auth** — Secure HMAC-SHA256 token validation for shell connections
42
43
  - šŸ“ **File System** — Built-in `ls`, `cd`, `pwd`, plus passthrough to your system shell
43
44
  - šŸ“œ **History** — Persisted command history at `~/.thinknsh_history`
@@ -411,9 +412,40 @@ Exit the shell gracefully (saves history, closes TCP notification server, discon
411
412
 
412
413
  ---
413
414
 
414
- ## `.tncproject` Auto-Boot
415
+ ### GitHub Integration Commands
415
416
 
416
- 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.
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
+
446
+ ## `tncproject` Auto-Boot
447
+
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.
417
449
 
418
450
  ```
419
451
  .tncproject
@@ -424,12 +456,12 @@ On startup the shell sends the encrypted config to the server, receives a shell
424
456
  ```
425
457
  šŸ“„ tncproject found
426
458
  Room : room-abc123
427
- File : /home/user/myproject/.tncproject
459
+ File : /home/user/myproject/tncproject
428
460
 
429
461
  āœ… Raman Singh → room "My Project Room"
430
462
  ```
431
463
 
432
- If `.tncproject` auth fails, the shell falls back to manual login.
464
+ If `tncproject` auth fails, the shell falls back to manual login.
433
465
 
434
466
  ---
435
467
 
@@ -528,8 +560,8 @@ thinknsh> notify "Daily standup in 5 mins"
528
560
  thinknsh> status
529
561
  thinknsh> exit
530
562
 
531
- # .tncproject auto-boot (no login needed)
532
- cd ~/myproject # contains .tncproject
563
+ # tncproject auto-boot (no login needed)
564
+ cd ~/myproject # contains tncproject
533
565
  thinknsh # auto-logs in + auto-joins room
534
566
 
535
567
  # Quick one-liners from CI / scripts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkncollab-shell",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
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