thinkncollab-shell 0.0.20 ā 2.0.0-beta.1.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.
- package/LICENSE +82 -0
- package/Readme.md +246 -502
- package/dist/bin/thinknsh.js +8 -0
- package/dist/src/commands/auth/login.js +7 -0
- package/dist/src/commands/auth/logout.js +7 -0
- package/dist/src/commands/auth/register.js +7 -0
- package/dist/src/commands/auth/whoami.js +7 -0
- package/dist/src/commands/dock/bench.js +7 -0
- package/dist/src/commands/dock/ci.js +7 -0
- package/dist/src/commands/dock/cluster.js +7 -0
- package/dist/src/commands/dock/containers.js +7 -0
- package/dist/src/commands/dock/dock.js +7 -0
- package/dist/src/commands/github/commits.js +7 -0
- package/dist/src/commands/github/issue-create.js +7 -0
- package/dist/src/commands/github/issues.js +7 -0
- package/dist/src/commands/github/pulls.js +7 -0
- package/dist/src/commands/github/statusgh.js +7 -0
- package/dist/src/commands/index.js +7 -0
- package/dist/src/commands/message/say.js +7 -0
- package/dist/src/commands/room/join.js +7 -0
- package/dist/src/commands/room/leave.js +7 -0
- package/dist/src/commands/room/rooms.js +7 -0
- package/dist/src/commands/room/sync.js +7 -0
- package/dist/src/commands/sandbox/console.js +7 -0
- package/dist/src/commands/sandbox/dep.js +7 -0
- package/dist/src/commands/sandbox/deploy.js +7 -0
- package/dist/src/commands/sandbox/dev.js +7 -0
- package/dist/src/commands/sandbox/exec.js +7 -0
- package/dist/src/commands/sandbox/freeze.js +7 -0
- package/dist/src/commands/sandbox/push.js +7 -0
- package/dist/src/commands/sandbox/resume.js +7 -0
- package/dist/src/commands/sandbox/sandbox.js +7 -0
- package/dist/src/commands/sandbox/share.js +7 -0
- package/dist/src/commands/sandbox/snapshot.js +7 -0
- package/dist/src/commands/sandbox/sync-deps.js +7 -0
- package/dist/src/commands/shell/doctor.js +7 -0
- package/dist/src/commands/shell/notifications.js +7 -0
- package/dist/src/commands/shell/notify.js +7 -0
- package/dist/src/commands/shell/status.js +7 -0
- package/dist/src/commands/shell/version.js +7 -0
- package/dist/src/commands/tasks/build.js +7 -0
- package/dist/src/commands/tasks/complete.js +7 -0
- package/dist/src/commands/tasks/create-task.js +7 -0
- package/dist/src/commands/tasks/loadtest.js +7 -0
- package/dist/src/commands/tasks/task-status.js +7 -0
- package/dist/src/commands/tasks/task.js +7 -0
- package/dist/src/commands/tasks/tasks.js +7 -0
- package/dist/src/commands/team/invite.js +7 -0
- package/dist/src/commands/team/myteam.js +7 -0
- package/dist/src/commands/team/teammates.js +7 -0
- package/dist/src/commands/terminal/share.js +7 -0
- package/dist/src/core/api-client.js +7 -0
- package/dist/src/core/crypto/e2ee-vault.js +7 -0
- package/dist/src/core/dep-store.js +7 -0
- package/dist/src/core/dependency-tracker.js +7 -0
- package/dist/src/core/geekload.js +7 -0
- package/dist/src/core/local-runner.js +7 -0
- package/dist/src/core/multi-lang-scanner.js +7 -0
- package/dist/src/core/notification-window.js +8 -0
- package/dist/src/core/notify-auth.js +7 -0
- package/dist/src/core/project-config.js +7 -0
- package/dist/src/core/sandbox/arch-shim.js +7 -0
- package/dist/src/core/sandbox/capsule-builder.js +7 -0
- package/dist/src/core/sandbox/cgroups.js +7 -0
- package/dist/src/core/sandbox/container-monitor.js +7 -0
- package/dist/src/core/sandbox/crun-manager.js +7 -0
- package/dist/src/core/sandbox/engine.js +7 -0
- package/dist/src/core/sandbox/image-manager.js +7 -0
- package/dist/src/core/sandbox/image-store.js +7 -0
- package/dist/src/core/sandbox/index.js +7 -0
- package/dist/src/core/sandbox/macos-sandbox.js +7 -0
- package/dist/src/core/sandbox/microvm-manager.js +7 -0
- package/dist/src/core/sandbox/native-service-manager.js +7 -0
- package/dist/src/core/sandbox/network.js +7 -0
- package/dist/src/core/sandbox/oci-exporter.js +7 -0
- package/dist/src/core/sandbox/oci-puller.js +7 -0
- package/dist/src/core/sandbox/oci-runtime-spec.js +7 -0
- package/dist/src/core/sandbox/policy-engine.js +7 -0
- package/dist/src/core/sandbox/port-proxy-daemon.js +7 -0
- package/dist/src/core/sandbox/room-sync-manager.js +7 -0
- package/dist/src/core/sandbox/runtime-manager.js +7 -0
- package/dist/src/core/sandbox/snapshot-manager.js +7 -0
- package/dist/src/core/sandbox/storage.js +7 -0
- package/dist/src/core/sandbox/supervisor.js +7 -0
- package/dist/src/core/sandbox/tunnel-daemon.js +7 -0
- package/dist/src/core/sandbox/tunnel.js +7 -0
- package/dist/src/core/sandbox/window-launcher.js +7 -0
- package/dist/src/core/sandbox/windows-wsl2.js +7 -0
- package/dist/src/core/sandbox-window.js +8 -0
- package/dist/src/core/shell.js +7 -0
- package/dist/src/core/websocket.js +7 -0
- package/dist/src/dock/api/routes.js +7 -0
- package/dist/src/dock/api/server.js +7 -0
- package/dist/src/dock/api/wire.js +7 -0
- package/dist/src/dock/bench/memory.js +7 -0
- package/dist/src/dock/bench/runner.js +7 -0
- package/dist/src/dock/build/builder.js +7 -0
- package/dist/src/dock/build/buildfile.js +7 -0
- package/dist/src/dock/build/buildkit.js +7 -0
- package/dist/src/dock/build/changes.js +7 -0
- package/dist/src/dock/ci/actions.js +7 -0
- package/dist/src/dock/ci/runner.js +7 -0
- package/dist/src/dock/ci/workflow.js +7 -0
- package/dist/src/dock/cli/api.js +7 -0
- package/dist/src/dock/cli/args.js +7 -0
- package/dist/src/dock/cli/commands.js +7 -0
- package/dist/src/dock/cli/compose.js +7 -0
- package/dist/src/dock/cli/context.js +7 -0
- package/dist/src/dock/cli/files.js +7 -0
- package/dist/src/dock/cli/format.js +7 -0
- package/dist/src/dock/cli/manage.js +7 -0
- package/dist/src/dock/cli/moved.js +7 -0
- package/dist/src/dock/cli/services.js +7 -0
- package/dist/src/dock/cli/session.js +7 -0
- package/dist/src/dock/cli/workspace.js +7 -0
- package/dist/src/dock/client.js +7 -0
- package/dist/src/dock/compat/cases.js +7 -0
- package/dist/src/dock/compat/runner.js +7 -0
- package/dist/src/dock/config.js +7 -0
- package/dist/src/dock/daemon-main.js +7 -0
- package/dist/src/dock/daemon.js +7 -0
- package/dist/src/dock/deploy/compose.js +7 -0
- package/dist/src/dock/engine.js +7 -0
- package/dist/src/dock/image/archive.js +7 -0
- package/dist/src/dock/image/auth.js +7 -0
- package/dist/src/dock/image/reference.js +7 -0
- package/dist/src/dock/image/registry.js +7 -0
- package/dist/src/dock/image/tar.js +7 -0
- package/dist/src/dock/index.js +7 -0
- package/dist/src/dock/linux/host.js +7 -0
- package/dist/src/dock/paths.js +7 -0
- package/dist/src/dock/runtime/emulation.js +7 -0
- package/dist/src/dock/runtime/fsevents.js +7 -0
- package/dist/src/dock/runtime/fsops.js +7 -0
- package/dist/src/dock/runtime/hostfwd.js +7 -0
- package/dist/src/dock/runtime/logs.js +7 -0
- package/dist/src/dock/runtime/network.js +7 -0
- package/dist/src/dock/runtime/ports.js +7 -0
- package/dist/src/dock/runtime/rootfs.js +7 -0
- package/dist/src/dock/runtime/seccomp-profile.LICENSE +202 -0
- package/dist/src/dock/runtime/seccomp-profile.json +1227 -0
- package/dist/src/dock/runtime/seccomp.js +7 -0
- package/dist/src/dock/runtime/spec.js +7 -0
- package/dist/src/dock/runtime/stats.js +7 -0
- package/dist/src/dock/runtime/volumes.js +7 -0
- package/dist/src/dock/selftest.js +7 -0
- package/dist/src/dock/setup/app.js +7 -0
- package/dist/src/dock/setup/autostart.js +7 -0
- package/dist/src/dock/setup/index.js +7 -0
- package/dist/src/dock/setup/integrate.js +7 -0
- package/dist/src/dock/setup/migrate.js +7 -0
- package/dist/src/dock/state/store.js +7 -0
- package/dist/src/dock/ui/page.js +385 -0
- package/dist/src/dock/ui/server.js +7 -0
- package/dist/src/dock/version.js +7 -0
- package/dist/src/dock/workspace/apps.js +7 -0
- package/dist/src/dock/workspace/workspace.js +7 -0
- package/dist/src/events/notifications.js +7 -0
- package/dist/src/utils/logger.js +7 -0
- package/dist/src/utils/terminalCrypto.js +7 -0
- package/package.json +41 -16
- package/bin/thinknsh.js +0 -71
- package/src/commands/auth/login.js +0 -84
- package/src/commands/auth/logout.js +0 -32
- package/src/commands/auth/register.js +0 -52
- package/src/commands/auth/whoami.js +0 -52
- package/src/commands/github/commits.js +0 -58
- package/src/commands/github/issue-create.js +0 -59
- package/src/commands/github/issues.js +0 -57
- package/src/commands/github/pulls.js +0 -58
- package/src/commands/github/statusgh.js +0 -50
- package/src/commands/index.js +0 -136
- package/src/commands/message/say.js +0 -49
- package/src/commands/room/join.js +0 -142
- package/src/commands/room/leave.js +0 -24
- package/src/commands/room/rooms.js +0 -66
- package/src/commands/shell/notify.js +0 -88
- package/src/commands/shell/status.js +0 -73
- package/src/commands/shell/version.js +0 -18
- package/src/commands/tasks/build.js +0 -190
- package/src/commands/tasks/complete.js +0 -940
- package/src/commands/tasks/create-task.js +0 -101
- package/src/commands/tasks/loadtest.js +0 -101
- package/src/commands/tasks/suggestTask.js +0 -0
- package/src/commands/tasks/task-status.js +0 -72
- package/src/commands/tasks/task.js +0 -182
- package/src/commands/tasks/tasks.js +0 -184
- package/src/commands/team/invite.js +0 -46
- package/src/commands/team/myteam.js +0 -78
- package/src/commands/team/teammates.js +0 -52
- package/src/commands/terminal/share.js +0 -261
- package/src/core/api-client.js +0 -246
- package/src/core/geekload.js +0 -524
- package/src/core/local-runner.js +0 -861
- package/src/core/notification-window.js +0 -165
- package/src/core/project-config.js +0 -100
- package/src/core/shell.js +0 -734
- package/src/core/websocket.js +0 -657
- package/src/events/notifications.js +0 -184
- package/src/utils/logger.js +0 -159
- package/src/utils/terminalCrypto.js +0 -47
package/src/core/shell.js
DELETED
|
@@ -1,734 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* src/core/shell.js ā ThinkNCollab Shell Core sound
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const fs = require('fs-extra');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const chalk = require('chalk');
|
|
9
|
-
const net = require('net');
|
|
10
|
-
const { spawn } = require('child_process');
|
|
11
|
-
const EventEmitter = require('events');
|
|
12
|
-
const WebSocketManager = require('./websocket');
|
|
13
|
-
const CommandRegistry = require('../commands');
|
|
14
|
-
const ThinkNCollabAPI = require('./api-client');
|
|
15
|
-
const { loadProjectConfig, hasProjectConfig } = require('./project-config');
|
|
16
|
-
|
|
17
|
-
function getAvailablePort(startPort) {
|
|
18
|
-
return new Promise((resolve) => {
|
|
19
|
-
const server = net.createServer();
|
|
20
|
-
server.listen(startPort, '127.0.0.1', () => {
|
|
21
|
-
const port = server.address().port;
|
|
22
|
-
server.close(() => resolve(port));
|
|
23
|
-
});
|
|
24
|
-
server.on('error', () => resolve(getAvailablePort(startPort + 1)));
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class ThinkNCollabShell extends EventEmitter {
|
|
29
|
-
|
|
30
|
-
constructor(options = {}) {
|
|
31
|
-
super();
|
|
32
|
-
|
|
33
|
-
this.config = {
|
|
34
|
-
historyFile: path.join(os.homedir(), '.thinknsh_history'),
|
|
35
|
-
configDir: path.join(os.homedir(), '.thinkncollab'),
|
|
36
|
-
maxHistory: 1000,
|
|
37
|
-
websocket: {
|
|
38
|
-
serverUrl: options.serverUrl || 'https://thinkncollab.com',
|
|
39
|
-
autoConnect: options.autoConnect || false
|
|
40
|
-
},
|
|
41
|
-
...options
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
fs.ensureDirSync(this.config.configDir);
|
|
45
|
-
|
|
46
|
-
this.ws = new WebSocketManager({ ...this.config.websocket, _shellRef: this });
|
|
47
|
-
this.currentDir = process.cwd();
|
|
48
|
-
this.username = os.userInfo().username;
|
|
49
|
-
this.history = [];
|
|
50
|
-
this.running = false;
|
|
51
|
-
|
|
52
|
-
// TCP notification IPC
|
|
53
|
-
this.notifyClients = new Set();
|
|
54
|
-
this.tcpServer = null;
|
|
55
|
-
|
|
56
|
-
this.api = new ThinkNCollabAPI({
|
|
57
|
-
apiUrl: options.apiUrl || 'https://thinkncollab.com',
|
|
58
|
-
wsUrl: options.wsUrl || 'https://thinkncollab.com'
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
this.commands = new Map();
|
|
62
|
-
this.aliases = new Map();
|
|
63
|
-
this.variables = new Map();
|
|
64
|
-
|
|
65
|
-
// Restore persisted session on startup
|
|
66
|
-
// Handles both .tncproject boot AND manual login from a previous session
|
|
67
|
-
if (this.api.isAuthenticated()) {
|
|
68
|
-
this.ws.setUser(this.api.getUser(), this.api.getToken());
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.loadHistory();
|
|
72
|
-
this.registerBuiltinCommands();
|
|
73
|
-
this.commandRegistry = new CommandRegistry(this);
|
|
74
|
-
this.setupWebSocketHandlers();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// āāā TCP Notification Server āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
78
|
-
|
|
79
|
-
async startTCPServer() {
|
|
80
|
-
this.notifyPort = await getAvailablePort(7379);
|
|
81
|
-
return new Promise((resolve) => {
|
|
82
|
-
this.tcpServer = net.createServer((socket) => {
|
|
83
|
-
this.notifyClients.add(socket);
|
|
84
|
-
socket.on('close', () => this.notifyClients.delete(socket));
|
|
85
|
-
socket.on('error', () => this.notifyClients.delete(socket));
|
|
86
|
-
});
|
|
87
|
-
this.tcpServer.listen(this.notifyPort, '127.0.0.1', () => resolve());
|
|
88
|
-
this.tcpServer.on('error', (err) => {
|
|
89
|
-
console.log(chalk.yellow(`ā ļø Notification server: ${err.message}`));
|
|
90
|
-
resolve();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
pushNotification(data) {
|
|
96
|
-
if (this.notifyClients.size === 0) return;
|
|
97
|
-
const line = JSON.stringify(data) + '\n';
|
|
98
|
-
for (const client of this.notifyClients) {
|
|
99
|
-
try { client.write(line); } catch { }
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// āāā Spawn notification window āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
104
|
-
spawnNotificationWindow() {
|
|
105
|
-
const notifyScript = path.join(__dirname, 'notification-window.js');
|
|
106
|
-
const port = this.notifyPort;
|
|
107
|
-
const env = { ...process.env, THINKNSH_NOTIFY_PORT: String(port) };
|
|
108
|
-
|
|
109
|
-
const isWSL = process.platform === 'linux' &&
|
|
110
|
-
(() => { try { return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft'); } catch { return false; } })();
|
|
111
|
-
|
|
112
|
-
const fallback = () => {
|
|
113
|
-
console.log(chalk.yellow(`\n ā ļø Could not open notification window.`));
|
|
114
|
-
console.log(chalk.dim(` Open a new terminal and run:`));
|
|
115
|
-
console.log(chalk.cyan(` THINKNSH_NOTIFY_PORT=${port} node "${notifyScript}"\n`));
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
// āā WSL āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
120
|
-
if (isWSL) {
|
|
121
|
-
const winScript = notifyScript
|
|
122
|
-
.replace(/^\/mnt\/([a-z])/, (_, d) => `${d.toUpperCase()}:`)
|
|
123
|
-
.replace(/\//g, '\\');
|
|
124
|
-
|
|
125
|
-
const launchWinNode = (winExe) => {
|
|
126
|
-
const p = spawn(
|
|
127
|
-
`start cmd.exe /k ""${winExe}" "${winScript}""`,
|
|
128
|
-
[],
|
|
129
|
-
{ detached: true, stdio: 'ignore', env, shell: true }
|
|
130
|
-
);
|
|
131
|
-
p.on('error', fallback);
|
|
132
|
-
p.unref();
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// 1. Known paths ā instant
|
|
136
|
-
const known = [
|
|
137
|
-
'/mnt/c/Program Files/nodejs/node.exe',
|
|
138
|
-
'/mnt/c/Program Files (x86)/nodejs/node.exe',
|
|
139
|
-
];
|
|
140
|
-
const found = known.find(p => { try { return fs.existsSync(p); } catch { return false; } });
|
|
141
|
-
|
|
142
|
-
if (found) {
|
|
143
|
-
const winExe = found
|
|
144
|
-
.replace(/^\/mnt\/([a-z])/, (_, d) => `${d.toUpperCase()}:`)
|
|
145
|
-
.replace(/\//g, '\\');
|
|
146
|
-
launchWinNode(winExe);
|
|
147
|
-
} else {
|
|
148
|
-
// 2. Async where node
|
|
149
|
-
const child = spawn('cmd.exe', ['/c', 'where', 'node'], {
|
|
150
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
151
|
-
});
|
|
152
|
-
let out = '';
|
|
153
|
-
child.stdout.on('data', d => out += d);
|
|
154
|
-
child.on('close', () => {
|
|
155
|
-
const winExe = out.trim().split('\n')[0]?.trim();
|
|
156
|
-
if (winExe?.endsWith('.exe')) {
|
|
157
|
-
launchWinNode(winExe);
|
|
158
|
-
} else {
|
|
159
|
-
// 3. Last resort
|
|
160
|
-
const p = spawn(
|
|
161
|
-
`start cmd.exe /k "wsl.exe -- bash -c 'THINKNSH_NOTIFY_PORT=${port} node \\"${notifyScript}\\"; read'"`,
|
|
162
|
-
[],
|
|
163
|
-
{ detached: true, stdio: 'ignore', shell: true }
|
|
164
|
-
);
|
|
165
|
-
p.on('error', fallback);
|
|
166
|
-
p.unref();
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// āā Windows native āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
172
|
-
} else if (process.platform === 'win32') {
|
|
173
|
-
const nodeExe = process.execPath;
|
|
174
|
-
const isVSCode = process.env.TERM_PROGRAM === 'vscode';
|
|
175
|
-
|
|
176
|
-
if (isVSCode) {
|
|
177
|
-
console.log(chalk.yellow('\n VS Code detected ā open a new terminal tab and run:'));
|
|
178
|
-
console.log(chalk.cyan(` $env:THINKNSH_NOTIFY_PORT=${port}; node "${notifyScript}"\n`));
|
|
179
|
-
} else {
|
|
180
|
-
const launchCmd = () => {
|
|
181
|
-
// shell:true ā Windows quoting issues handle ho jaate hain
|
|
182
|
-
const p = spawn(
|
|
183
|
-
`start cmd.exe /k ""${nodeExe}" "${notifyScript}""`,
|
|
184
|
-
[],
|
|
185
|
-
{ detached: true, stdio: 'ignore', env, shell: true }
|
|
186
|
-
);
|
|
187
|
-
p.on('error', fallback);
|
|
188
|
-
p.unref();
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const wt = spawn('wt.exe', [
|
|
192
|
-
'cmd.exe', '/k', `"${nodeExe}" "${notifyScript}"`
|
|
193
|
-
], { detached: true, stdio: 'ignore', env });
|
|
194
|
-
wt.on('error', launchCmd);
|
|
195
|
-
wt.unref();
|
|
196
|
-
}
|
|
197
|
-
// āā macOS āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
198
|
-
} else if (process.platform === 'darwin') {
|
|
199
|
-
const nodeExe = process.execPath;
|
|
200
|
-
|
|
201
|
-
// Better VS Code detection - multiple checks
|
|
202
|
-
const isVSCode = process.env.TERM_PROGRAM === 'vscode' ||
|
|
203
|
-
process.env.VSCODE_INJECTION === '1' ||
|
|
204
|
-
process.env.TERM_PROGRAM?.includes('vscode') ||
|
|
205
|
-
process.env.VSCODE_CWD !== undefined;
|
|
206
|
-
|
|
207
|
-
if (isVSCode) {
|
|
208
|
-
console.log(chalk.yellow(`\n ā ļø VS Code terminal detected`));
|
|
209
|
-
console.log(chalk.dim(` Notification window can't auto-open in VS Code.`));
|
|
210
|
-
console.log(chalk.cyan(` Please open Terminal.app and run:\n`));
|
|
211
|
-
console.log(chalk.white(` THINKNSH_NOTIFY_PORT=${port} node "${notifyScript}"\n`));
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Rest of the macOS code for Terminal.app/iTerm2
|
|
216
|
-
const terminalScript = `
|
|
217
|
-
tell application "Terminal"
|
|
218
|
-
do script "THINKNSH_NOTIFY_PORT=${port} '${nodeExe}' '${notifyScript}'"
|
|
219
|
-
activate
|
|
220
|
-
end tell
|
|
221
|
-
`;
|
|
222
|
-
try {
|
|
223
|
-
spawn('osascript', ['-e', terminalScript], {
|
|
224
|
-
detached: true, stdio: 'ignore'
|
|
225
|
-
}).unref();
|
|
226
|
-
} catch {
|
|
227
|
-
// iTerm2 fallback
|
|
228
|
-
const itermScript = `
|
|
229
|
-
tell application "iTerm2"
|
|
230
|
-
tell current window
|
|
231
|
-
create tab with default profile
|
|
232
|
-
tell current session of current tab
|
|
233
|
-
write text "THINKNSH_NOTIFY_PORT=${port} '${nodeExe}' '${notifyScript}'"
|
|
234
|
-
end tell
|
|
235
|
-
end tell
|
|
236
|
-
end tell
|
|
237
|
-
`;
|
|
238
|
-
try {
|
|
239
|
-
spawn('osascript', ['-e', itermScript], {
|
|
240
|
-
detached: true, stdio: 'ignore'
|
|
241
|
-
}).unref();
|
|
242
|
-
} catch {
|
|
243
|
-
console.log(chalk.yellow(` ā ļø Could not open notification window`));
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// āā Linux native āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
248
|
-
} else {
|
|
249
|
-
const nodeExe = process.execPath;
|
|
250
|
-
const bashCmd = `THINKNSH_NOTIFY_PORT=${port} '${nodeExe}' '${notifyScript}'; read`;
|
|
251
|
-
const terminals = [
|
|
252
|
-
['gnome-terminal', ['--', 'bash', '-c', bashCmd]],
|
|
253
|
-
['konsole', ['--noclose', '-e', 'bash', '-c', bashCmd]],
|
|
254
|
-
['xfce4-terminal', ['--hold', '-e', `bash -c "${bashCmd}"`]],
|
|
255
|
-
['tilix', ['-e', `bash -c "${bashCmd}"`]],
|
|
256
|
-
['alacritty', ['-e', 'bash', '-c', bashCmd]],
|
|
257
|
-
['kitty', ['bash', '-c', bashCmd]],
|
|
258
|
-
['xterm', ['-hold', '-e', `bash -c "${bashCmd}"`]],
|
|
259
|
-
];
|
|
260
|
-
|
|
261
|
-
let spawned = false;
|
|
262
|
-
for (const [bin, args] of terminals) {
|
|
263
|
-
try {
|
|
264
|
-
spawn(bin, args, { detached: true, stdio: 'ignore', env }).unref();
|
|
265
|
-
spawned = true;
|
|
266
|
-
break;
|
|
267
|
-
} catch { }
|
|
268
|
-
}
|
|
269
|
-
if (!spawned) fallback();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
} catch (err) {
|
|
273
|
-
fallback();
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// āāā WebSocket ā Shell + TCP bridge āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
278
|
-
|
|
279
|
-
setupWebSocketHandlers() {
|
|
280
|
-
this.ws.on('connected', (d) => {
|
|
281
|
-
this.api.logEvent('connected', null, { socketId: d?.socketId });
|
|
282
|
-
this.api.startHeartbeat();
|
|
283
|
-
this.pushNotification({ type: 'connected', socketId: d?.socketId });
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
this.ws.on('disconnected', (d) => {
|
|
287
|
-
this.api.logEvent('disconnected', null, { reason: d?.reason });
|
|
288
|
-
this.api.stopHeartbeat();
|
|
289
|
-
this.pushNotification({ type: 'disconnected', reason: d?.reason });
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
this.ws.on('reconnected', () => {
|
|
293
|
-
this.api.logEvent('reconnected');
|
|
294
|
-
this.api.startHeartbeat();
|
|
295
|
-
this.pushNotification({ type: 'connected', socketId: 'reconnected' });
|
|
296
|
-
});
|
|
297
|
-
this.ws.on('task:verdict', (d) => {
|
|
298
|
-
const passed = d.passed;
|
|
299
|
-
const border = passed ? chalk.green('ā'.repeat(52)) : chalk.red('ā'.repeat(52));
|
|
300
|
-
const badge = passed ? chalk.bgGreen.black(' PASS ') : chalk.bgRed.white(' FAIL ');
|
|
301
|
-
|
|
302
|
-
console.log('\n' + border);
|
|
303
|
-
console.log(` ${badge} ${chalk.bold(d.title || 'Task')} ${passed ? chalk.green('ā
PASSED') : chalk.red('ā FAILED')}`);
|
|
304
|
-
console.log(` ${chalk.dim('Reason :')} ${passed ? chalk.green(d.reason) : chalk.red(d.reason)}`);
|
|
305
|
-
if (d.diff) {
|
|
306
|
-
console.log(` ${chalk.dim('Expected:')} ${chalk.cyan(JSON.stringify(d.diff.expected ?? d.diff.conditions))}`);
|
|
307
|
-
console.log(` ${chalk.dim('Actual :')} ${chalk.yellow(JSON.stringify(d.diff.actual))}`);
|
|
308
|
-
}
|
|
309
|
-
console.log(` ${chalk.dim('At :')} ${chalk.dim(new Date(d.at || Date.now()).toLocaleTimeString())}`);
|
|
310
|
-
console.log(border + '\n');
|
|
311
|
-
|
|
312
|
-
this.pushNotification({
|
|
313
|
-
type: 'verdict',
|
|
314
|
-
passed,
|
|
315
|
-
title: d.title,
|
|
316
|
-
reason: d.reason,
|
|
317
|
-
taskId: d.taskId,
|
|
318
|
-
at: d.at
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
this.ws.on('message', (d) => {
|
|
323
|
-
const myId = this.api.getUser()?._id;
|
|
324
|
-
if (myId && d.userId === myId) return;
|
|
325
|
-
this.pushNotification({ type: 'message', from: d.username, text: d.message });
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
this.ws.on('messageHistory', (messages) => {
|
|
329
|
-
if (!messages?.length) return;
|
|
330
|
-
console.log(chalk.dim('\n āāā Room History āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
331
|
-
messages.forEach(msg => {
|
|
332
|
-
const from = msg.name || msg.username || msg.sender?.name || 'Unknown';
|
|
333
|
-
const text = msg.message || msg.content || msg.text || '';
|
|
334
|
-
const ts = msg.timestamp || msg.createdAt;
|
|
335
|
-
const timeStr = ts ? chalk.dim(`[${new Date(ts).toLocaleTimeString()}]`) : '';
|
|
336
|
-
console.log(` ${timeStr} ${chalk.cyan(from + ':')} ${chalk.white(text)}`);
|
|
337
|
-
});
|
|
338
|
-
console.log(chalk.dim(' āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
this.ws.on('userJoined', (d) => this.pushNotification({ type: 'userJoined', username: d.username }));
|
|
342
|
-
this.ws.on('userLeft', (d) => this.pushNotification({ type: 'userLeft', username: d.username }));
|
|
343
|
-
|
|
344
|
-
// room join/leave events
|
|
345
|
-
this.ws.on('joined', (roomId) => this.api.logEvent('room_join', roomId));
|
|
346
|
-
this.ws.on('left', (roomId) => this.api.logEvent('room_leave', roomId));
|
|
347
|
-
|
|
348
|
-
this.ws.on('notification', (d) => {
|
|
349
|
-
this.pushNotification({
|
|
350
|
-
type: d.type || 'notification',
|
|
351
|
-
level: d.level,
|
|
352
|
-
title: d.title,
|
|
353
|
-
message: d.message,
|
|
354
|
-
taskTitle: d.taskTitle,
|
|
355
|
-
assignedBy: d.assignedBy,
|
|
356
|
-
meta: d.meta,
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
this.ws.on('error', (e) => {
|
|
361
|
-
console.log(chalk.red(`ā ļø WebSocket error: ${e.message}`));
|
|
362
|
-
this.pushNotification({ type: 'notification', level: 'error', title: 'WebSocket Error', message: e.message });
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// āāā Start āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
367
|
-
|
|
368
|
-
async start() {
|
|
369
|
-
this.showWelcome();
|
|
370
|
-
|
|
371
|
-
await this.startTCPServer();
|
|
372
|
-
console.log(chalk.dim(` Notification server ready on port ${this.notifyPort}`));
|
|
373
|
-
|
|
374
|
-
this.spawnNotificationWindow();
|
|
375
|
-
console.log(chalk.dim(' Notification window opening...\n'));
|
|
376
|
-
|
|
377
|
-
this.running = true;
|
|
378
|
-
process.on('SIGINT', () => console.log(chalk.yellow('\nUse "exit" to quit')));
|
|
379
|
-
|
|
380
|
-
// Try .tncproject auto-boot first
|
|
381
|
-
const booted = await this._bootFromProjectConfig();
|
|
382
|
-
|
|
383
|
-
// If no .tncproject but autoConnect is set, connect WS anyway
|
|
384
|
-
if (!booted && this.config.websocket.autoConnect) {
|
|
385
|
-
await this.ws.connect().catch(() => { });
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const { input } = require('@inquirer/prompts');
|
|
389
|
-
|
|
390
|
-
while (this.running) {
|
|
391
|
-
try {
|
|
392
|
-
const line = await input({ message: this.getPromptText(), required: false }).catch(() => null);
|
|
393
|
-
if (line === null) { console.log(chalk.yellow('Use "exit" to quit')); continue; }
|
|
394
|
-
const trimmed = line.trim();
|
|
395
|
-
if (!trimmed) continue;
|
|
396
|
-
this.history.push(trimmed);
|
|
397
|
-
this.saveHistory();
|
|
398
|
-
await this.execute(trimmed);
|
|
399
|
-
} catch (err) {
|
|
400
|
-
if (err.name === 'ExitPromptError' || err.message?.includes('force closed'))
|
|
401
|
-
console.log(chalk.yellow('Use "exit" to quit'));
|
|
402
|
-
else
|
|
403
|
-
console.log(chalk.red(`Shell error: ${err.message}`));
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// āāā Boot from .tncproject āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
409
|
-
|
|
410
|
-
async _bootFromProjectConfig() {
|
|
411
|
-
if (!hasProjectConfig()) return false;
|
|
412
|
-
|
|
413
|
-
let cfg;
|
|
414
|
-
try {
|
|
415
|
-
cfg = loadProjectConfig();
|
|
416
|
-
} catch (err) {
|
|
417
|
-
console.log(chalk.yellow(`ā ļø tncproject: ${err.message}\n`));
|
|
418
|
-
return false;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
console.log(chalk.dim(` š tncproject found`));
|
|
422
|
-
console.log(chalk.dim(` Room : ${cfg.roomId}`));
|
|
423
|
-
console.log(chalk.dim(` File : ${cfg.filePath}\n`));
|
|
424
|
-
|
|
425
|
-
// Send raw encrypted file to server ā server decrypts, returns shellToken
|
|
426
|
-
let result;
|
|
427
|
-
try {
|
|
428
|
-
result = await this.api._request('POST', '/config/verify-config', {
|
|
429
|
-
fileBase64: cfg.fileBase64,
|
|
430
|
-
roomId: cfg.roomId,
|
|
431
|
-
machineId: os.hostname(),
|
|
432
|
-
});
|
|
433
|
-
} catch (err) {
|
|
434
|
-
console.log(chalk.red(`ā tncproject auth failed: ${err.message}`));
|
|
435
|
-
console.log(chalk.dim(' Falling back to manual login.\n'));
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Save session exactly like manual login
|
|
440
|
-
this.api._saveSession({
|
|
441
|
-
token: result.shellToken,
|
|
442
|
-
shellSessionId: result.shellSessionId || null,
|
|
443
|
-
user: {
|
|
444
|
-
_id: result._id.toString(),
|
|
445
|
-
userId: result._id.toString(),
|
|
446
|
-
email: result.email,
|
|
447
|
-
name: result.name,
|
|
448
|
-
userType: 'User',
|
|
449
|
-
},
|
|
450
|
-
timestamp: new Date().toISOString(),
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
this.ws.setUser(this.api.getUser(), result.shellToken);
|
|
454
|
-
|
|
455
|
-
// Connect WebSocket
|
|
456
|
-
try {
|
|
457
|
-
await this.ws.connect(result.shellToken);
|
|
458
|
-
} catch (err) {
|
|
459
|
-
console.log(chalk.yellow(`ā ļø WebSocket: ${err.message}`));
|
|
460
|
-
console.log(chalk.green(`ā
${result.name} logged in (offline mode)\n`));
|
|
461
|
-
return true;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Auto-join the project room
|
|
465
|
-
try {
|
|
466
|
-
await this.ws.joinRoom(result.roomId.toString());
|
|
467
|
-
console.log(chalk.green(`ā
${result.name} ā room "${result.roomName || result.roomId}"\n`));
|
|
468
|
-
} catch (err) {
|
|
469
|
-
console.log(chalk.yellow(`ā ļø Could not auto-join room: ${err.message}`));
|
|
470
|
-
console.log(chalk.dim(' Use "join <room-id>" manually.\n'));
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return true;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
async execute(input) {
|
|
477
|
-
if (!input.trim()) return;
|
|
478
|
-
|
|
479
|
-
const args = this.parseCommand(input);
|
|
480
|
-
const cmdName = args[0].toLowerCase();
|
|
481
|
-
const cmdArgs = args.slice(1);
|
|
482
|
-
|
|
483
|
-
// Skip sensitive commands
|
|
484
|
-
const skipLog = ['login', 'logout', 'set', 'unset'];
|
|
485
|
-
if (!skipLog.includes(cmdName)) {
|
|
486
|
-
await this.api.logEvent('command', null, { cmd: cmdName });
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (this.commands.has(cmdName)) {
|
|
490
|
-
try { await this.commands.get(cmdName).handler(cmdArgs, this); }
|
|
491
|
-
catch (err) { console.log(chalk.red(`Error: ${err.message}`)); }
|
|
492
|
-
process.stdout.write('\r\n');
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
if (this.commandRegistry) {
|
|
497
|
-
const handled = await this.commandRegistry.execute(input, this);
|
|
498
|
-
if (handled) {
|
|
499
|
-
process.stdout.write('\r\n');
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const blockedOnWindows = ['open', 'which', 'grep', 'touch', 'cat'];
|
|
505
|
-
if (process.platform === 'win32' && blockedOnWindows.includes(cmdName)) {
|
|
506
|
-
console.log(chalk.red(`'${cmdName}' is not a thinknsh command.`));
|
|
507
|
-
console.log(chalk.dim(` Type 'help' to see available commands.`));
|
|
508
|
-
process.stdout.write('\r\n');
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
await this.executeSystemCommand(input);
|
|
513
|
-
}
|
|
514
|
-
// āāā Prompt āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
515
|
-
|
|
516
|
-
getPromptText() {
|
|
517
|
-
let p = '';
|
|
518
|
-
const room = this.ws.getCurrentRoom?.();
|
|
519
|
-
if (room) p += `[${room}] `;
|
|
520
|
-
if (this.ws.isConnected?.()) p += 'ā ';
|
|
521
|
-
|
|
522
|
-
let displayPath = this.currentDir;
|
|
523
|
-
const homeDir = os.homedir();
|
|
524
|
-
|
|
525
|
-
if (process.platform === 'win32') {
|
|
526
|
-
if (displayPath.toLowerCase() === homeDir.toLowerCase()) displayPath = '~';
|
|
527
|
-
else if (displayPath.toLowerCase().startsWith(homeDir.toLowerCase()))
|
|
528
|
-
displayPath = '~' + displayPath.slice(homeDir.length).replace(/\\/g, '/');
|
|
529
|
-
else displayPath = displayPath.replace(/\\/g, '/');
|
|
530
|
-
} else {
|
|
531
|
-
displayPath = displayPath.replace(homeDir, '~');
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return `${this.username}@thinknsh:${displayPath} $`;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
getPrompt() { return this.getPromptText(); }
|
|
538
|
-
printMessage(msg) { console.log(msg); }
|
|
539
|
-
|
|
540
|
-
// āāā Built-ins āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
541
|
-
|
|
542
|
-
registerBuiltinCommands() {
|
|
543
|
-
const r = (name, handler, desc) => this.commands.set(name, { handler, description: desc });
|
|
544
|
-
|
|
545
|
-
r('help', this.helpCommand.bind(this), 'Show available commands');
|
|
546
|
-
r('exit', () => { this.cleanup(); process.exit(0); }, 'Exit the shell');
|
|
547
|
-
r('quit', () => { this.cleanup(); process.exit(0); }, 'Exit the shell');
|
|
548
|
-
r('clear', () => console.clear(), 'Clear screen');
|
|
549
|
-
r('history', this.historyCommand.bind(this), 'Show command history');
|
|
550
|
-
r('cd', this.cdCommand.bind(this), 'Change directory');
|
|
551
|
-
r('pwd', () => console.log(process.cwd()), 'Print working directory');
|
|
552
|
-
r('ls', this.lsCommand.bind(this), 'List files');
|
|
553
|
-
r('connect', this.connectCommand.bind(this), 'Connect to WebSocket');
|
|
554
|
-
r('disconnect', () => this.ws.disconnect(), 'Disconnect WebSocket');
|
|
555
|
-
r('status', this.statusCommand.bind(this), 'Show connection status');
|
|
556
|
-
r('join', this.joinCommand.bind(this), 'Join a room');
|
|
557
|
-
r('leave', () => this.ws.leaveRoom?.(), 'Leave current room');
|
|
558
|
-
r('rooms', this.roomsCommand.bind(this), 'List rooms');
|
|
559
|
-
r('say', this.sayCommand.bind(this), 'Send a message');
|
|
560
|
-
r('msg', this.sayCommand.bind(this), 'Send a message');
|
|
561
|
-
r('set', this.setCommand.bind(this), 'Set a variable');
|
|
562
|
-
r('unset', this.unsetCommand.bind(this), 'Unset a variable');
|
|
563
|
-
|
|
564
|
-
this.addAlias('ll', 'ls -la');
|
|
565
|
-
this.addAlias('..', 'cd ..');
|
|
566
|
-
this.addAlias('~', 'cd ~');
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
registerCommand(name, handler, description = '') { this.commands.set(name, { handler, description }); }
|
|
570
|
-
addAlias(alias, command) { this.aliases.set(alias, command); }
|
|
571
|
-
|
|
572
|
-
async helpCommand() {
|
|
573
|
-
if (this.commandRegistry) console.log(this.commandRegistry.getHelpText());
|
|
574
|
-
console.log(chalk.dim(' Any unrecognised command runs in your system shell\n'));
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
historyCommand() {
|
|
578
|
-
this.history.forEach((cmd, i) => console.log(`${(i + 1).toString().padStart(4)} ${cmd}`));
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
async cdCommand(args) {
|
|
582
|
-
const target = args.join(' ').trim() || os.homedir();
|
|
583
|
-
try {
|
|
584
|
-
let newPath;
|
|
585
|
-
if (target === '~') newPath = os.homedir();
|
|
586
|
-
else if (target === '..') newPath = path.dirname(this.currentDir);
|
|
587
|
-
else newPath = path.resolve(this.currentDir, target);
|
|
588
|
-
newPath = path.normalize(newPath);
|
|
589
|
-
const stats = await fs.stat(newPath);
|
|
590
|
-
if (!stats.isDirectory()) { console.log(chalk.red(`cd: not a directory: ${target}`)); return; }
|
|
591
|
-
this.previousDir = this.currentDir;
|
|
592
|
-
process.chdir(newPath);
|
|
593
|
-
this.currentDir = process.cwd();
|
|
594
|
-
} catch { console.log(chalk.red(`cd: no such directory: ${target}`)); }
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
async lsCommand(args) {
|
|
598
|
-
try {
|
|
599
|
-
const files = await fs.readdir(this.currentDir);
|
|
600
|
-
const showAll = args.includes('-a');
|
|
601
|
-
const filtered = showAll ? files : files.filter(f => !f.startsWith('.'));
|
|
602
|
-
const fileList = await Promise.all(filtered.map(async (file) => {
|
|
603
|
-
const stat = await fs.stat(path.join(this.currentDir, file));
|
|
604
|
-
return { name: file, isDir: stat.isDirectory() };
|
|
605
|
-
}));
|
|
606
|
-
fileList.forEach(f => f.isDir ? console.log(chalk.blue(f.name + '/')) : console.log(f.name));
|
|
607
|
-
} catch (err) { console.log(chalk.red(`ls: ${err.message}`)); }
|
|
608
|
-
}
|
|
609
|
-
// tncproject auth failed: Could not read .tncproject ā re-download it from the dashboard
|
|
610
|
-
async connectCommand() {
|
|
611
|
-
console.log(chalk.cyan('š Connecting...'));
|
|
612
|
-
try { await this.ws.connect(); }
|
|
613
|
-
catch (err) { console.log(chalk.red(`ā ${err.message}`)); }
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
async statusCommand() {
|
|
617
|
-
const connected = this.ws.isConnected();
|
|
618
|
-
console.log(chalk.cyan('\nš Status:'));
|
|
619
|
-
console.log(` WebSocket : ${connected ? chalk.green('ā Connected') : chalk.red('ā Disconnected')}`);
|
|
620
|
-
if (connected) console.log(` Socket ID : ${chalk.dim(this.ws.getSocketId())}`);
|
|
621
|
-
const room = this.ws.getCurrentRoom();
|
|
622
|
-
console.log(` Room : ${room ? chalk.green(room) : chalk.yellow('None')}`);
|
|
623
|
-
console.log(` Auth : ${this.api.isAuthenticated() ? chalk.green('ā Logged in') : chalk.yellow('Not logged in')}`);
|
|
624
|
-
const user = this.api.getUser();
|
|
625
|
-
if (user) console.log(` User : ${chalk.dim(user.name || user.email)}`);
|
|
626
|
-
console.log('');
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
async joinCommand(args) {
|
|
630
|
-
if (!args[0]) { console.log(chalk.red('Usage: join <room-id>')); return; }
|
|
631
|
-
if (!this.ws.isConnected()) { console.log(chalk.yellow('Connecting...')); await this.ws.connect(); }
|
|
632
|
-
try {
|
|
633
|
-
const result = await this.ws.joinRoom(args[0]);
|
|
634
|
-
console.log(chalk.green(`ā
Joined: ${result.roomName || args[0]}`));
|
|
635
|
-
} catch (err) { console.log(chalk.red(`ā ${err.message}`)); }
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
async roomsCommand() {
|
|
639
|
-
if (!this.ws.isConnected()) { console.log(chalk.yellow('Not connected')); return; }
|
|
640
|
-
this.ws.socket.emit('rooms:list');
|
|
641
|
-
this.ws.socket.once('rooms:list', (data) => {
|
|
642
|
-
if (data.rooms?.length) data.rooms.forEach(r => console.log(` ${chalk.cyan(r.name || r.id)} ā š„ ${r.participantCount || 0}`));
|
|
643
|
-
else console.log(' No rooms');
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
async sayCommand(args) {
|
|
648
|
-
const message = args.join(' ');
|
|
649
|
-
if (!message) { console.log(chalk.red('Usage: say <message>')); return; }
|
|
650
|
-
try {
|
|
651
|
-
await this.ws.sendMessage(message);
|
|
652
|
-
const displayName = this.api.getUser()?.name || this.username;
|
|
653
|
-
console.log(chalk.green(`You: ${message}`));
|
|
654
|
-
this.pushNotification({ type: 'message', from: `You (${displayName})`, text: message });
|
|
655
|
-
} catch (err) { console.log(chalk.red(`Failed: ${err.message}`)); }
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
async setCommand(args) {
|
|
659
|
-
if (!args.length) { for (const [k, v] of this.variables) console.log(` ${chalk.green(k.padEnd(12))} = ${v}`); return; }
|
|
660
|
-
this.variables.set(args[0], args.slice(1).join(' '));
|
|
661
|
-
console.log(chalk.green(`${args[0]} = ${args.slice(1).join(' ')}`));
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
async unsetCommand(args) {
|
|
665
|
-
if (!args[0]) return;
|
|
666
|
-
this.variables.delete(args[0]);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
getSessionPath() { return path.join(this.config.configDir, 'session.json'); }
|
|
670
|
-
|
|
671
|
-
parseCommand(input) {
|
|
672
|
-
const args = [];
|
|
673
|
-
let current = '', inQuotes = false, quoteChar = '', escapeNext = false;
|
|
674
|
-
for (const char of input) {
|
|
675
|
-
if (char === '\\' && !escapeNext && !inQuotes) { escapeNext = true; continue; }
|
|
676
|
-
if (escapeNext) { current += char; escapeNext = false; continue; }
|
|
677
|
-
if ((char === '"' || char === "'") && !inQuotes) { inQuotes = true; quoteChar = char; continue; }
|
|
678
|
-
if (char === quoteChar && inQuotes) { inQuotes = false; quoteChar = ''; continue; }
|
|
679
|
-
if (char === ' ' && !inQuotes) { if (current) { args.push(current); current = ''; } continue; }
|
|
680
|
-
current += char;
|
|
681
|
-
}
|
|
682
|
-
if (current) args.push(current);
|
|
683
|
-
return args;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
executeSystemCommand(command) {
|
|
687
|
-
return new Promise((resolve) => {
|
|
688
|
-
const child = spawn(command, [], {
|
|
689
|
-
cwd: this.currentDir,
|
|
690
|
-
shell: true,
|
|
691
|
-
stdio: 'inherit'
|
|
692
|
-
});
|
|
693
|
-
child.on('close', (code) => {
|
|
694
|
-
// Update currentDir in case command changed it (cd won't but others might) http://localhost:3001
|
|
695
|
-
try { this.currentDir = process.cwd(); } catch { }
|
|
696
|
-
process.stdout.write('\r\n');
|
|
697
|
-
resolve();
|
|
698
|
-
});
|
|
699
|
-
child.on('error', () => {
|
|
700
|
-
console.log(chalk.red(`Command not found: ${command}`));
|
|
701
|
-
resolve();
|
|
702
|
-
});
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
showWelcome() {
|
|
707
|
-
try { const figlet = require('figlet'); console.log(chalk.cyan(figlet.textSync('ThinkNCollab', { font: 'Standard' }))); } catch { }
|
|
708
|
-
console.log(chalk.cyan(`
|
|
709
|
-
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
710
|
-
ā ThinkNCollab Shell v0.0.10 ā
|
|
711
|
-
ā Type 'help' for commands ā
|
|
712
|
-
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`));
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
loadHistory() {
|
|
716
|
-
try {
|
|
717
|
-
if (fs.existsSync(this.config.historyFile))
|
|
718
|
-
this.history = fs.readFileSync(this.config.historyFile, 'utf8').split('\n').filter(l => l.trim());
|
|
719
|
-
} catch { }
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
saveHistory() {
|
|
723
|
-
try { fs.writeFileSync(this.config.historyFile, this.history.slice(-this.config.maxHistory).join('\n')); } catch { }
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
cleanup() {
|
|
727
|
-
this.api.logout(); // session end + heartbeat stop + lastSeen update
|
|
728
|
-
if (this.tcpServer) { try { this.tcpServer.close(); } catch { } }
|
|
729
|
-
if (this.ws) this.ws.disconnect();
|
|
730
|
-
this.saveHistory();
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
module.exports = ThinkNCollabShell;
|