thinkncollab-shell 0.0.19 → 2.0.0-beta.1

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.
Files changed (201) hide show
  1. package/LICENSE +82 -0
  2. package/Readme.md +246 -502
  3. package/dist/bin/thinknsh.js +8 -0
  4. package/dist/src/commands/auth/login.js +7 -0
  5. package/dist/src/commands/auth/logout.js +7 -0
  6. package/dist/src/commands/auth/register.js +7 -0
  7. package/dist/src/commands/auth/whoami.js +7 -0
  8. package/dist/src/commands/dock/bench.js +7 -0
  9. package/dist/src/commands/dock/ci.js +7 -0
  10. package/dist/src/commands/dock/cluster.js +7 -0
  11. package/dist/src/commands/dock/containers.js +7 -0
  12. package/dist/src/commands/dock/dock.js +7 -0
  13. package/dist/src/commands/github/commits.js +7 -0
  14. package/dist/src/commands/github/issue-create.js +7 -0
  15. package/dist/src/commands/github/issues.js +7 -0
  16. package/dist/src/commands/github/pulls.js +7 -0
  17. package/dist/src/commands/github/statusgh.js +7 -0
  18. package/dist/src/commands/index.js +7 -0
  19. package/dist/src/commands/message/say.js +7 -0
  20. package/dist/src/commands/room/join.js +7 -0
  21. package/dist/src/commands/room/leave.js +7 -0
  22. package/dist/src/commands/room/rooms.js +7 -0
  23. package/dist/src/commands/room/sync.js +7 -0
  24. package/dist/src/commands/sandbox/console.js +7 -0
  25. package/dist/src/commands/sandbox/dep.js +7 -0
  26. package/dist/src/commands/sandbox/deploy.js +7 -0
  27. package/dist/src/commands/sandbox/dev.js +7 -0
  28. package/dist/src/commands/sandbox/exec.js +7 -0
  29. package/dist/src/commands/sandbox/freeze.js +7 -0
  30. package/dist/src/commands/sandbox/push.js +7 -0
  31. package/dist/src/commands/sandbox/resume.js +7 -0
  32. package/dist/src/commands/sandbox/sandbox.js +7 -0
  33. package/dist/src/commands/sandbox/share.js +7 -0
  34. package/dist/src/commands/sandbox/snapshot.js +7 -0
  35. package/dist/src/commands/sandbox/sync-deps.js +7 -0
  36. package/dist/src/commands/shell/doctor.js +7 -0
  37. package/dist/src/commands/shell/notifications.js +7 -0
  38. package/dist/src/commands/shell/notify.js +7 -0
  39. package/dist/src/commands/shell/status.js +7 -0
  40. package/dist/src/commands/shell/version.js +7 -0
  41. package/dist/src/commands/tasks/build.js +7 -0
  42. package/dist/src/commands/tasks/complete.js +7 -0
  43. package/dist/src/commands/tasks/create-task.js +7 -0
  44. package/dist/src/commands/tasks/loadtest.js +7 -0
  45. package/dist/src/commands/tasks/task-status.js +7 -0
  46. package/dist/src/commands/tasks/task.js +7 -0
  47. package/dist/src/commands/tasks/tasks.js +7 -0
  48. package/dist/src/commands/team/invite.js +7 -0
  49. package/dist/src/commands/team/myteam.js +7 -0
  50. package/dist/src/commands/team/teammates.js +7 -0
  51. package/dist/src/commands/terminal/share.js +7 -0
  52. package/dist/src/core/api-client.js +7 -0
  53. package/dist/src/core/crypto/e2ee-vault.js +7 -0
  54. package/dist/src/core/dep-store.js +7 -0
  55. package/dist/src/core/dependency-tracker.js +7 -0
  56. package/dist/src/core/geekload.js +7 -0
  57. package/dist/src/core/local-runner.js +7 -0
  58. package/dist/src/core/multi-lang-scanner.js +7 -0
  59. package/dist/src/core/notification-window.js +8 -0
  60. package/dist/src/core/notify-auth.js +7 -0
  61. package/dist/src/core/project-config.js +7 -0
  62. package/dist/src/core/sandbox/arch-shim.js +7 -0
  63. package/dist/src/core/sandbox/capsule-builder.js +7 -0
  64. package/dist/src/core/sandbox/cgroups.js +7 -0
  65. package/dist/src/core/sandbox/container-monitor.js +7 -0
  66. package/dist/src/core/sandbox/crun-manager.js +7 -0
  67. package/dist/src/core/sandbox/engine.js +7 -0
  68. package/dist/src/core/sandbox/image-manager.js +7 -0
  69. package/dist/src/core/sandbox/image-store.js +7 -0
  70. package/dist/src/core/sandbox/index.js +7 -0
  71. package/dist/src/core/sandbox/macos-sandbox.js +7 -0
  72. package/dist/src/core/sandbox/microvm-manager.js +7 -0
  73. package/dist/src/core/sandbox/native-service-manager.js +7 -0
  74. package/dist/src/core/sandbox/network.js +7 -0
  75. package/dist/src/core/sandbox/oci-exporter.js +7 -0
  76. package/dist/src/core/sandbox/oci-puller.js +7 -0
  77. package/dist/src/core/sandbox/oci-runtime-spec.js +7 -0
  78. package/dist/src/core/sandbox/policy-engine.js +7 -0
  79. package/dist/src/core/sandbox/port-proxy-daemon.js +7 -0
  80. package/dist/src/core/sandbox/room-sync-manager.js +7 -0
  81. package/dist/src/core/sandbox/runtime-manager.js +7 -0
  82. package/dist/src/core/sandbox/snapshot-manager.js +7 -0
  83. package/dist/src/core/sandbox/storage.js +7 -0
  84. package/dist/src/core/sandbox/supervisor.js +7 -0
  85. package/dist/src/core/sandbox/tunnel-daemon.js +7 -0
  86. package/dist/src/core/sandbox/tunnel.js +7 -0
  87. package/dist/src/core/sandbox/window-launcher.js +7 -0
  88. package/dist/src/core/sandbox/windows-wsl2.js +7 -0
  89. package/dist/src/core/sandbox-window.js +8 -0
  90. package/dist/src/core/shell.js +7 -0
  91. package/dist/src/core/websocket.js +7 -0
  92. package/dist/src/dock/api/routes.js +7 -0
  93. package/dist/src/dock/api/server.js +7 -0
  94. package/dist/src/dock/api/wire.js +7 -0
  95. package/dist/src/dock/bench/memory.js +7 -0
  96. package/dist/src/dock/bench/runner.js +7 -0
  97. package/dist/src/dock/build/builder.js +7 -0
  98. package/dist/src/dock/build/buildfile.js +7 -0
  99. package/dist/src/dock/build/buildkit.js +7 -0
  100. package/dist/src/dock/build/changes.js +7 -0
  101. package/dist/src/dock/ci/actions.js +7 -0
  102. package/dist/src/dock/ci/runner.js +7 -0
  103. package/dist/src/dock/ci/workflow.js +7 -0
  104. package/dist/src/dock/cli/api.js +7 -0
  105. package/dist/src/dock/cli/args.js +7 -0
  106. package/dist/src/dock/cli/commands.js +7 -0
  107. package/dist/src/dock/cli/compose.js +7 -0
  108. package/dist/src/dock/cli/context.js +7 -0
  109. package/dist/src/dock/cli/files.js +7 -0
  110. package/dist/src/dock/cli/format.js +7 -0
  111. package/dist/src/dock/cli/manage.js +7 -0
  112. package/dist/src/dock/cli/moved.js +7 -0
  113. package/dist/src/dock/cli/services.js +7 -0
  114. package/dist/src/dock/cli/session.js +7 -0
  115. package/dist/src/dock/cli/workspace.js +7 -0
  116. package/dist/src/dock/client.js +7 -0
  117. package/dist/src/dock/compat/cases.js +7 -0
  118. package/dist/src/dock/compat/runner.js +7 -0
  119. package/dist/src/dock/config.js +7 -0
  120. package/dist/src/dock/daemon-main.js +7 -0
  121. package/dist/src/dock/daemon.js +7 -0
  122. package/dist/src/dock/deploy/compose.js +7 -0
  123. package/dist/src/dock/engine.js +7 -0
  124. package/dist/src/dock/image/archive.js +7 -0
  125. package/dist/src/dock/image/auth.js +7 -0
  126. package/dist/src/dock/image/reference.js +7 -0
  127. package/dist/src/dock/image/registry.js +7 -0
  128. package/dist/src/dock/image/tar.js +7 -0
  129. package/dist/src/dock/index.js +7 -0
  130. package/dist/src/dock/linux/host.js +7 -0
  131. package/dist/src/dock/paths.js +7 -0
  132. package/dist/src/dock/runtime/emulation.js +7 -0
  133. package/dist/src/dock/runtime/fsevents.js +7 -0
  134. package/dist/src/dock/runtime/fsops.js +7 -0
  135. package/dist/src/dock/runtime/hostfwd.js +7 -0
  136. package/dist/src/dock/runtime/logs.js +7 -0
  137. package/dist/src/dock/runtime/network.js +7 -0
  138. package/dist/src/dock/runtime/ports.js +7 -0
  139. package/dist/src/dock/runtime/rootfs.js +7 -0
  140. package/dist/src/dock/runtime/seccomp-profile.LICENSE +202 -0
  141. package/dist/src/dock/runtime/seccomp-profile.json +1227 -0
  142. package/dist/src/dock/runtime/seccomp.js +7 -0
  143. package/dist/src/dock/runtime/spec.js +7 -0
  144. package/dist/src/dock/runtime/stats.js +7 -0
  145. package/dist/src/dock/runtime/volumes.js +7 -0
  146. package/dist/src/dock/selftest.js +7 -0
  147. package/dist/src/dock/setup/app.js +7 -0
  148. package/dist/src/dock/setup/autostart.js +7 -0
  149. package/dist/src/dock/setup/index.js +7 -0
  150. package/dist/src/dock/setup/integrate.js +7 -0
  151. package/dist/src/dock/setup/migrate.js +7 -0
  152. package/dist/src/dock/state/store.js +7 -0
  153. package/dist/src/dock/ui/page.js +385 -0
  154. package/dist/src/dock/ui/server.js +7 -0
  155. package/dist/src/dock/version.js +7 -0
  156. package/dist/src/dock/workspace/apps.js +7 -0
  157. package/dist/src/dock/workspace/workspace.js +7 -0
  158. package/dist/src/events/notifications.js +7 -0
  159. package/dist/src/utils/logger.js +7 -0
  160. package/dist/src/utils/terminalCrypto.js +7 -0
  161. package/package.json +41 -16
  162. package/bin/thinknsh.js +0 -71
  163. package/src/commands/auth/login.js +0 -84
  164. package/src/commands/auth/logout.js +0 -32
  165. package/src/commands/auth/register.js +0 -52
  166. package/src/commands/auth/whoami.js +0 -52
  167. package/src/commands/github/commits.js +0 -58
  168. package/src/commands/github/issue-create.js +0 -59
  169. package/src/commands/github/issues.js +0 -57
  170. package/src/commands/github/pulls.js +0 -58
  171. package/src/commands/github/statusgh.js +0 -50
  172. package/src/commands/index.js +0 -136
  173. package/src/commands/message/say.js +0 -49
  174. package/src/commands/room/join.js +0 -142
  175. package/src/commands/room/leave.js +0 -24
  176. package/src/commands/room/rooms.js +0 -66
  177. package/src/commands/shell/notify.js +0 -88
  178. package/src/commands/shell/status.js +0 -73
  179. package/src/commands/shell/version.js +0 -18
  180. package/src/commands/tasks/build.js +0 -190
  181. package/src/commands/tasks/complete.js +0 -940
  182. package/src/commands/tasks/create-task.js +0 -101
  183. package/src/commands/tasks/loadtest.js +0 -101
  184. package/src/commands/tasks/suggestTask.js +0 -0
  185. package/src/commands/tasks/task-status.js +0 -72
  186. package/src/commands/tasks/task.js +0 -182
  187. package/src/commands/tasks/tasks.js +0 -184
  188. package/src/commands/team/invite.js +0 -46
  189. package/src/commands/team/myteam.js +0 -78
  190. package/src/commands/team/teammates.js +0 -52
  191. package/src/commands/terminal/share.js +0 -261
  192. package/src/core/api-client.js +0 -246
  193. package/src/core/geekload.js +0 -524
  194. package/src/core/local-runner.js +0 -861
  195. package/src/core/notification-window.js +0 -165
  196. package/src/core/project-config.js +0 -100
  197. package/src/core/shell.js +0 -734
  198. package/src/core/websocket.js +0 -482
  199. package/src/events/notifications.js +0 -184
  200. package/src/utils/logger.js +0 -159
  201. package/src/utils/terminalCrypto.js +0 -47
@@ -1,46 +0,0 @@
1
- /*
2
- Invite Command
3
-
4
- Usage: invite <email>
5
- invite <email> --role <role>
6
- */
7
-
8
- const chalk = require('chalk');
9
-
10
- module.exports = {
11
- name: 'invite',
12
- description: 'Invite someone to your team',
13
- aliases: ['inv'],
14
- requiresAuth: true,
15
-
16
- async execute(args, shell) {
17
- if (args.length === 0) {
18
- console.log(chalk.red('❌ Usage: invite <email> [--role <role>]'));
19
- console.log(chalk.dim(' Roles: admin, member, viewer'));
20
- return;
21
- }
22
-
23
- const email = args[0];
24
- const roleIndex = args.indexOf('--role');
25
- const role = roleIndex !== -1 && args[roleIndex + 1] ? args[roleIndex + 1] : 'member';
26
-
27
- console.log(chalk.cyan(`📧 Inviting ${email} as ${role}...`));
28
-
29
- try {
30
- const result = await shell.api.inviteTeammate(email, role);
31
-
32
- console.log(chalk.green(`✅ Invitation sent to ${email}`));
33
-
34
- if (result.inviteLink) {
35
- console.log(chalk.dim(`📎 Invite link: ${result.inviteLink}`));
36
- }
37
-
38
- } catch (error) {
39
- if (error.message.includes('already')) {
40
- console.log(chalk.yellow(`⚠️ ${email} is already a team member`));
41
- } else {
42
- console.log(chalk.red(`❌ Invitation failed: ${error.message}`));
43
- }
44
- }
45
- }
46
- };
@@ -1,78 +0,0 @@
1
- /*
2
- MyTeam Command
3
-
4
- Usage: myteam
5
- myteam [team-id]
6
- */
7
-
8
- const chalk = require('chalk');
9
- const Table = require('cli-table3');
10
-
11
- module.exports = {
12
- name: 'myteam',
13
- description: 'Show team information and members',
14
- aliases: ['team', 'org'],
15
- requiresAuth: true,
16
-
17
- async execute(args, shell) {
18
- const teamId = args[0] || shell.api.getUser()?.team;
19
-
20
- if (!teamId) {
21
- console.log(chalk.yellow('⚠️ You are not part of any team'));
22
- console.log(chalk.dim('💡 Ask your team admin for an invite'));
23
- return;
24
- }
25
-
26
- console.log(chalk.cyan(`\n👥 Team Information: ${teamId}`));
27
- console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
28
-
29
- try {
30
- const teamInfo = await shell.api.getTeamInfo(teamId);
31
-
32
- // Team details
33
- console.log(` ${chalk.yellow('Name:')} ${teamInfo.name}`);
34
- console.log(` ${chalk.yellow('Members:')} ${teamInfo.memberCount}`);
35
- console.log(` ${chalk.yellow('Rooms:')} ${teamInfo.roomCount}`);
36
- console.log(` ${chalk.yellow('Created:')} ${new Date(teamInfo.createdAt).toLocaleDateString()}`);
37
-
38
- if (teamInfo.description) {
39
- console.log(` ${chalk.yellow('Description:')} ${teamInfo.description}`);
40
- }
41
-
42
- console.log();
43
-
44
- // Team members table
45
- const table = new Table({
46
- head: ['Status', 'Name', 'Email', 'Role', 'Current Room'],
47
- colWidths: [8, 20, 25, 12, 20]
48
- });
49
-
50
- teamInfo.members.forEach(member => {
51
- const status = member.online ? chalk.green('●') : chalk.gray('○');
52
- const room = member.currentRoom || '-';
53
- table.push([
54
- status,
55
- member.name || member.email.split('@')[0],
56
- member.email,
57
- member.role || 'member',
58
- room
59
- ]);
60
- });
61
-
62
- console.log(table.toString());
63
- console.log();
64
-
65
- // Team activity
66
- if (teamInfo.recentActivity && teamInfo.recentActivity.length > 0) {
67
- console.log(chalk.cyan('📊 Recent Activity:'));
68
- teamInfo.recentActivity.slice(0, 5).forEach(activity => {
69
- const time = new Date(activity.timestamp).toLocaleTimeString();
70
- console.log(chalk.dim(` [${time}] ${activity.user}: ${activity.action}`));
71
- });
72
- }
73
-
74
- } catch (error) {
75
- console.log(chalk.red(`❌ Failed to fetch team info: ${error.message}`));
76
- }
77
- }
78
- };
@@ -1,52 +0,0 @@
1
- /*
2
- Teammates Command
3
-
4
- Usage: teammates
5
- teammates [name]
6
- */
7
-
8
- const chalk = require('chalk');
9
-
10
- module.exports = {
11
- name: 'teammates',
12
- description: 'List online teammates',
13
- aliases: ['members', 'online'],
14
- requiresAuth: true,
15
-
16
- async execute(args, shell) {
17
- const filter = args[0] ? args[0].toLowerCase() : null;
18
-
19
- console.log(chalk.cyan('\n👥 Online Teammates:'));
20
- console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
21
-
22
- try {
23
- const teammates = await shell.api.getTeammates();
24
-
25
- const online = teammates.filter(t => t.online);
26
- const offline = teammates.filter(t => !t.online);
27
-
28
- if (online.length === 0) {
29
- console.log(chalk.yellow(' No teammates online'));
30
- } else {
31
- online
32
- .filter(t => !filter || t.name.toLowerCase().includes(filter) || t.email.toLowerCase().includes(filter))
33
- .forEach(t => {
34
- const typing = t.typing ? chalk.green(' (typing...)') : '';
35
- const room = t.currentRoom ? chalk.dim(` in ${t.currentRoom}`) : '';
36
- console.log(` ${chalk.green('●')} ${chalk.white(t.name || t.email)}${typing}${room}`);
37
-
38
- if (t.status && t.status !== 'online') {
39
- console.log(chalk.dim(` ${t.status}`));
40
- }
41
- });
42
- }
43
-
44
- if (offline.length > 0 && !filter) {
45
- console.log(chalk.dim(`\n ... and ${offline.length} offline`));
46
- }
47
-
48
- } catch (error) {
49
- console.log(chalk.red(`❌ Failed to fetch teammates: ${error.message}`));
50
- }
51
- }
52
- };
@@ -1,261 +0,0 @@
1
- /*
2
- commands/terminal/share.js
3
-
4
- Usage:
5
- share start — start sharing current terminal
6
- share stop — stop sharing
7
- share status — show who's viewing / requesting
8
- share grant <uid> — grant write to a viewer
9
- share revoke <uid>— revoke write
10
- share kick <uid> — kick a viewer
11
- */
12
-
13
- const chalk = require('chalk');
14
-
15
- module.exports = {
16
- name: 'share',
17
- description: 'Share your terminal with room members',
18
- aliases: ['terminal-share', 'ts'],
19
- requiresAuth: true,
20
- requiresRoom: true,
21
-
22
- async execute(args, shell) {
23
- const sub = (args[0] || '').toLowerCase();
24
-
25
- switch (sub) {
26
- case 'start': return cmdStart(shell);
27
- case 'stop': return cmdStop(shell);
28
- case 'status': return cmdStatus(shell);
29
- case 'grant': return cmdGrant(args[1], shell);
30
- case 'revoke': return cmdRevoke(args[1], shell);
31
- case 'kick': return cmdKick(args[1], shell);
32
- default:
33
- printHelp();
34
- }
35
- }
36
- };
37
-
38
- // ── start ─────────────────────────────────────────────────────────────────────
39
- async function cmdStart(shell) {
40
- if (shell._shareActive) {
41
- console.log(chalk.yellow('⚠️ Already sharing. Run "share stop" first.'));
42
- return;
43
- }
44
-
45
- const roomId = shell.ws.getCurrentRoom();
46
- if (!roomId) {
47
- console.log(chalk.red('❌ Join a room first: join <room-id>'));
48
- return;
49
- }
50
-
51
- return new Promise((resolve) => {
52
- const timeout = setTimeout(() => {
53
- shell.ws.socket.off('terminal:share:started', onStarted);
54
- console.log(chalk.red('❌ Share start timeout'));
55
- resolve();
56
- }, 6000);
57
-
58
- function onStarted(data) {
59
- clearTimeout(timeout);
60
- shell._shareActive = true;
61
- shell._shareSessionId = data.sessionId;
62
-
63
- console.log(chalk.green(`\n✅ Terminal sharing started`));
64
- console.log(chalk.dim(` Session : ${data.sessionId}`));
65
- console.log(chalk.dim(` Room : ${roomId}`));
66
- console.log(chalk.dim(` URL : https://thinkncollab.com/terminal/${data.sessionId}`));
67
- console.log(chalk.dim(`\n Room members can now view your terminal.`));
68
- console.log(chalk.dim(` Use "share grant <userId>" to give write access.\n`));
69
-
70
- // Hook into shell output to stream it
71
- _hookOutput(shell, data.sessionId);
72
-
73
- resolve();
74
- }
75
-
76
- shell.ws.socket.once('terminal:share:started', onStarted);
77
- shell.ws.socket.emit('terminal:share:start', { roomId });
78
- });
79
- }
80
-
81
- // ── stop ──────────────────────────────────────────────────────────────────────
82
- async function cmdStop(shell) {
83
- if (!shell._shareActive) {
84
- console.log(chalk.yellow('⚠️ No active share session.'));
85
- return;
86
- }
87
-
88
- shell.ws.socket.emit('terminal:share:stop');
89
- _unhookOutput(shell);
90
- shell._shareActive = false;
91
- shell._shareSessionId = null;
92
- console.log(chalk.green('✅ Terminal sharing stopped.'));
93
- }
94
-
95
- // ── status ────────────────────────────────────────────────────────────────────
96
- async function cmdStatus(shell) {
97
- if (!shell._shareActive || !shell._shareSessionId) {
98
- console.log(chalk.yellow('// No active share session'));
99
- return;
100
- }
101
-
102
- const mem = shell._shareViewers || {};
103
- const viewers = Object.values(mem);
104
-
105
- console.log(chalk.cyan(`\n Terminal Share — ${shell._shareSessionId}`));
106
- console.log(chalk.dim(' ' + '─'.repeat(44)));
107
-
108
- if (!viewers.length) {
109
- console.log(chalk.dim(' No viewers yet.'));
110
- } else {
111
- viewers.forEach(v => {
112
- const access = v.canWrite ? chalk.blue('[write]') : chalk.dim('[read]');
113
- const req = v.requested ? chalk.yellow(' ← requesting write') : '';
114
- console.log(` ${chalk.green('●')} ${chalk.white(v.name)} ${access}${req}`);
115
- console.log(chalk.dim(` userId: ${v.userId}`));
116
- });
117
- }
118
- console.log();
119
- }
120
-
121
- // ── grant ─────────────────────────────────────────────────────────────────────
122
- async function cmdGrant(userId, shell) {
123
- if (!userId) { console.log(chalk.red('Usage: share grant <userId>')); return; }
124
- if (!shell._shareActive) { console.log(chalk.red('❌ No active share session')); return; }
125
-
126
- shell.ws.socket.emit('terminal:grant', {
127
- sessionId: shell._shareSessionId,
128
- userId,
129
- });
130
-
131
- // Update local viewer state
132
- if (shell._shareViewers?.[userId]) {
133
- shell._shareViewers[userId].canWrite = true;
134
- shell._shareViewers[userId].requested = false;
135
- }
136
-
137
- console.log(chalk.green(`✅ Write access granted to ${userId}`));
138
- }
139
-
140
- // ── revoke ────────────────────────────────────────────────────────────────────
141
- async function cmdRevoke(userId, shell) {
142
- if (!userId) { console.log(chalk.red('Usage: share revoke <userId>')); return; }
143
- if (!shell._shareActive) { console.log(chalk.red('❌ No active share session')); return; }
144
-
145
- shell.ws.socket.emit('terminal:revoke', {
146
- sessionId: shell._shareSessionId,
147
- userId,
148
- });
149
-
150
- if (shell._shareViewers?.[userId]) {
151
- shell._shareViewers[userId].canWrite = false;
152
- }
153
-
154
- console.log(chalk.green(`✅ Write access revoked from ${userId}`));
155
- }
156
-
157
- // ── kick ──────────────────────────────────────────────────────────────────────
158
- async function cmdKick(userId, shell) {
159
- if (!userId) { console.log(chalk.red('Usage: share kick <userId>')); return; }
160
- if (!shell._shareActive) { console.log(chalk.red('❌ No active share session')); return; }
161
-
162
- shell.ws.socket.emit('terminal:kick', {
163
- sessionId: shell._shareSessionId,
164
- userId,
165
- });
166
-
167
- if (shell._shareViewers) delete shell._shareViewers[userId];
168
- console.log(chalk.green(`✅ ${userId} kicked from session`));
169
- }
170
-
171
- // ── Output hooking ────────────────────────────────────────────────────────────
172
- // Intercept process.stdout.write to stream output to viewers
173
-
174
- let _originalWrite = null;
175
-
176
- function _hookOutput(shell, sessionId) {
177
- shell._shareViewers = {};
178
- _originalWrite = process.stdout.write.bind(process.stdout);
179
-
180
- // Remove any existing listeners to prevent duplicates on re-share
181
- shell.ws.socket.off('terminal:viewerJoined');
182
- shell.ws.socket.off('terminal:viewerLeft');
183
- shell.ws.socket.off('terminal:writeRequest');
184
- shell.ws.socket.off('terminal:input');
185
-
186
- process.stdout.write = function(chunk, encoding, callback) {
187
- const str = chunk.toString();
188
- // Send to viewers — stream everything including ANSI colors
189
- if (shell.ws.isConnected() && shell._shareActive) {
190
- if (shell._e2eeEnabled && shell._aesKey) {
191
- const terminalCrypto = require('../../utils/terminalCrypto');
192
- const encrypted = terminalCrypto.encryptPayload(str, shell._aesKey);
193
- shell.ws.socket.emit('terminal:output:encrypted', {
194
- sessionId,
195
- ...encrypted
196
- });
197
- } else {
198
- shell.ws.socket.emit('terminal:output', {
199
- sessionId,
200
- data: str,
201
- });
202
- }
203
- }
204
- // Still write to own terminal
205
- return _originalWrite(chunk, encoding, callback);
206
- };
207
-
208
- // Listen for viewer events
209
- shell.ws.socket.on('terminal:viewerJoined', (d) => {
210
- if (!shell._shareViewers) shell._shareViewers = {};
211
- shell._shareViewers[d.userId] = { userId: d.userId, name: d.name, canWrite: false, requested: false };
212
- console.log(chalk.dim(`\n 👁️ ${d.name} is now viewing your terminal`));
213
- });
214
-
215
- shell.ws.socket.on('terminal:viewerLeft', (d) => {
216
- if (shell._shareViewers) delete shell._shareViewers[d.userId];
217
- console.log(chalk.dim(`\n 👁️ ${d.name} stopped viewing`));
218
- });
219
-
220
- shell.ws.socket.on('terminal:writeRequest', (d) => {
221
- if (shell._shareViewers?.[d.userId]) shell._shareViewers[d.userId].requested = true;
222
- console.log(chalk.yellow(`\n ✍️ ${d.name} is requesting write access`));
223
- console.log(chalk.dim(` share grant ${d.userId} or share kick ${d.userId}\n`));
224
- });
225
-
226
- shell.ws.socket.on('terminal:input', (d) => {
227
- // A writer sent input — execute it in the shell
228
- console.log(chalk.blue(`\n [${d.fromName}] → ${d.input}`));
229
- // The shell will process this as if the user typed it
230
- shell.execute(d.input.trim());
231
- });
232
- }
233
-
234
- function _unhookOutput(shell) {
235
- if (_originalWrite) {
236
- process.stdout.write = _originalWrite;
237
- _originalWrite = null;
238
- }
239
- shell.ws.socket.off('terminal:viewerJoined');
240
- shell.ws.socket.off('terminal:viewerLeft');
241
- shell.ws.socket.off('terminal:writeRequest');
242
- shell.ws.socket.off('terminal:input');
243
- }
244
-
245
- // ── Help ──────────────────────────────────────────────────────────────────────
246
- function printHelp() {
247
- console.log(chalk.cyan('\n Terminal Share Commands'));
248
- console.log(chalk.dim(' ' + '─'.repeat(40)));
249
- const rows = [
250
- ['share start', 'Start sharing your terminal'],
251
- ['share stop', 'Stop sharing'],
252
- ['share status', 'Show viewers and write requests'],
253
- ['share grant <userId>', 'Give write access to a viewer'],
254
- ['share revoke <userId>','Revoke write access'],
255
- ['share kick <userId>', 'Remove a viewer'],
256
- ];
257
- rows.forEach(([cmd, desc]) => {
258
- console.log(` ${chalk.green(cmd.padEnd(26))} ${chalk.dim(desc)}`);
259
- });
260
- console.log();
261
- }
@@ -1,246 +0,0 @@
1
- /**
2
- * src/core/api-client.js
3
- */
4
-
5
- const https = require('https');
6
- const http = require('http');
7
- const path = require('path');
8
- const fs = require('fs-extra');
9
- const os = require('os');
10
-
11
- class ThinkNCollabAPI {
12
-
13
- constructor(options = {}) {
14
- this.apiUrl = (options.apiUrl || 'https://thinkncollab.com').replace(/\/$/, '');
15
- this.wsUrl = (options.wsUrl || 'https://thinkncollab.com').replace(/\/$/, '');
16
- this.session = null;
17
- this.sessionPath = path.join(os.homedir(), '.thinkncollab', 'session.json');
18
- this._loadSession();
19
- }
20
-
21
- // ─── Session ──────────────────────────────────────────────────────────────
22
-
23
- _loadSession() {
24
- try {
25
- if (fs.existsSync(this.sessionPath))
26
- this.session = fs.readJsonSync(this.sessionPath);
27
- } catch {}
28
- }
29
-
30
- _saveSession(session) {
31
- try {
32
- fs.ensureDirSync(path.dirname(this.sessionPath));
33
- fs.writeJsonSync(this.sessionPath, session);
34
- this.session = session;
35
- } catch {}
36
- }
37
-
38
- _clearSession() {
39
- try { fs.removeSync(this.sessionPath); } catch {}
40
- this.session = null;
41
- }
42
-
43
- isAuthenticated() { return !!(this.session?.token); }
44
- getToken() { return this.session?.token || null; }
45
-
46
- /*
47
- Returns user in a CONSISTENT shape used everywhere:
48
- { _id, userId, email, name, userType }
49
-
50
- Both _id and userId are the same string so callers can use either.
51
- */
52
- getUser() {
53
- const u = this.session?.user;
54
- if (!u) return null;
55
- const id = (u._id || u.userId || '').toString();
56
- return {
57
- _id: id,
58
- userId: id, // ws.setUser / _emitJoin use userId
59
- email: u.email || '',
60
- name: u.name || '',
61
- userType: u.userType || u.model || 'User',
62
- };
63
- }
64
-
65
- // ─── HTTP helper ──────────────────────────────────────────────────────────
66
-
67
- _request(method, endpoint, body = null, headers = {}) {
68
- return new Promise((resolve, reject) => {
69
- const url = new URL(this.apiUrl + endpoint);
70
- const isHttps = url.protocol === 'https:';
71
- const lib = isHttps ? https : http;
72
- const bodyStr = body ? JSON.stringify(body) : null;
73
-
74
- const options = {
75
- hostname: url.hostname,
76
- port: url.port || (isHttps ? 443 : 80),
77
- path: url.pathname + url.search,
78
- method,
79
- headers: {
80
- 'Content-Type': 'application/json',
81
- 'Accept': 'application/json',
82
- ...(this.session?.token ? { 'x-shell-token': this.session.token } : {}),
83
- ...headers,
84
- ...(bodyStr ? { 'Content-Length': Buffer.byteLength(bodyStr) } : {})
85
- }
86
- };
87
-
88
- const req = lib.request(options, (res) => {
89
- let data = '';
90
- res.on('data', chunk => data += chunk);
91
- res.on('end', () => {
92
- try {
93
- const parsed = JSON.parse(data);
94
- if (res.statusCode >= 400)
95
- reject(new Error(parsed.error || parsed.message || `HTTP ${res.statusCode}`));
96
- else
97
- resolve(parsed);
98
- } catch {
99
- reject(new Error(`Invalid response: ${data.slice(0, 100)}`));
100
- }
101
- });
102
- });
103
-
104
- req.on('error', (err) => {
105
- if (err.code === 'ECONNREFUSED')
106
- reject(new Error(`Cannot connect to server at ${this.apiUrl} — is it running?`));
107
- else
108
- reject(err);
109
- });
110
-
111
- if (bodyStr) req.write(bodyStr);
112
- req.end();
113
- });
114
- }
115
-
116
- // ─── Auth ─────────────────────────────────────────────────────────────────
117
-
118
- async login(email, password) {
119
- const result = await this._request('POST', '/thinknsh/login', null, {
120
- 'x-email': email,
121
- 'x-password': password,
122
- 'x-machine-id': os.hostname()
123
- });
124
-
125
- this._saveSession({
126
- token: result.shellToken,
127
- shellSessionId: result.shellSessionId,
128
- user: {
129
- _id: result._id,
130
- userId: result._id,
131
- email: result.email,
132
- name: result.name,
133
- userType: result.userType || result.model || 'User',
134
- },
135
- timestamp: new Date().toISOString()
136
- });
137
-
138
- return {
139
- token: result.shellToken,
140
- user: this.getUser()
141
- };
142
- }
143
-
144
- // ─── Session event logger ─────────────────────────────────────────────────
145
-
146
- async logEvent(type, roomId = null, meta = {}) {
147
- const sessionId = this.session?.shellSessionId;
148
- if (!sessionId) return;
149
- await this._request('POST', '/thinknsh/session/event', {
150
- sessionId, type, roomId, meta
151
- }).catch(() => {});
152
- }
153
-
154
- // ─── Heartbeat — keeps lastSeenAt fresh while shell is open ──────────────
155
-
156
- startHeartbeat(intervalMs = 60000) {
157
- if (this._heartbeatTimer) return; // already running
158
- this._heartbeatTimer = setInterval(() => {
159
- const sessionId = this.session?.shellSessionId;
160
- if (!sessionId) return;
161
- this._request('POST', '/thinknsh/session/event', {
162
- sessionId,
163
- type: 'heartbeat',
164
- meta: { ts: new Date().toISOString() }
165
- }).catch(() => {});
166
- }, intervalMs);
167
- }
168
-
169
- stopHeartbeat() {
170
- if (this._heartbeatTimer) {
171
- clearInterval(this._heartbeatTimer);
172
- this._heartbeatTimer = null;
173
- }
174
- }
175
-
176
- async logout() {
177
- const sessionId = this.session?.shellSessionId;
178
- if (sessionId) {
179
- await this._request('POST', '/thinknsh/session/end', { sessionId }).catch(() => {});
180
- }
181
- this.stopHeartbeat();
182
- this._clearSession();
183
- return { success: true };
184
- }
185
- async whoami() {
186
- if (!this.isAuthenticated()) throw new Error('Not logged in');
187
- return this.getUser();
188
- }
189
-
190
- // ─── Rooms ────────────────────────────────────────────────────────────────
191
-
192
- /**
193
- * joinRoom — validates membership server-side and returns room meta.
194
- * Called by commands/room/join.js BEFORE the WebSocket thinknsh:join emit.
195
- */
196
- async joinRoom(roomId, password) {
197
- if (!roomId) throw new Error('roomId is required');
198
-
199
- const result = await this._request('POST', `/thinknsh/rooms/${roomId}/join`,
200
- password ? { password } : null
201
- );
202
-
203
- // Normalise response — server may return different shapes
204
- return {
205
- room: result.room || result,
206
- participants: result.participants || result.members || [],
207
- recentMessages: result.recentMessages || result.history || [],
208
- };
209
- }
210
-
211
- /**
212
- * getRooms — list rooms the user belongs to
213
- */
214
- async getRooms() {
215
- return this._request('GET', '/thinknsh/rooms');
216
- }
217
-
218
- // ─── Tasks ────────────────────────────────────────────────────────────────
219
-
220
- async mytasks(roomId, limit) {
221
- if (!roomId) throw new Error('roomId is required');
222
- const user = this.getUser();
223
- let ep = `/thinknsh/tasks?roomId=${encodeURIComponent(roomId)}`;
224
- if (user?._id) ep += `&userId=${encodeURIComponent(user._id)}`;
225
- if (limit) ep += `&n=${limit}`;
226
- return this._request('GET', ep);
227
- }
228
-
229
- /**
230
- * Generic request wrapper for task command modules that call
231
- * shell.api.request(method, path, { params, body })
232
- */
233
- async request(method, endpoint, options = {}) {
234
- let ep = endpoint;
235
-
236
- // Append query params
237
- if (options.params && Object.keys(options.params).length) {
238
- const qs = new URLSearchParams(options.params).toString();
239
- ep += (ep.includes('?') ? '&' : '?') + qs;
240
- }
241
-
242
- return this._request(method.toUpperCase(), ep, options.body || null);
243
- }
244
- }
245
-
246
- module.exports = ThinkNCollabAPI;