vortix 1.0.4 → 1.1.0
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/agent/agent.js +820 -134
- package/agent/package.json +2 -1
- package/bin/vortix.js +81 -21
- package/package.json +19 -3
package/agent/agent.js
CHANGED
|
@@ -4,6 +4,7 @@ const os = require("os");
|
|
|
4
4
|
const readline = require("readline");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
|
+
const screenshot = require("screenshot-desktop");
|
|
7
8
|
|
|
8
9
|
process.on("uncaughtException", (err) => {
|
|
9
10
|
console.error("Uncaught Exception:", err);
|
|
@@ -15,6 +16,12 @@ process.on("unhandledRejection", (err) => {
|
|
|
15
16
|
|
|
16
17
|
const CONFIG_FILE = path.join(os.homedir(), '.vortix-config.json');
|
|
17
18
|
|
|
19
|
+
// Detect platform
|
|
20
|
+
const PLATFORM = os.platform(); // 'win32', 'darwin', 'linux'
|
|
21
|
+
const IS_WINDOWS = PLATFORM === 'win32';
|
|
22
|
+
const IS_MAC = PLATFORM === 'darwin';
|
|
23
|
+
const IS_LINUX = PLATFORM === 'linux';
|
|
24
|
+
|
|
18
25
|
// Load or create config
|
|
19
26
|
function loadConfig() {
|
|
20
27
|
try {
|
|
@@ -75,9 +82,27 @@ if (command === "start") {
|
|
|
75
82
|
return;
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
if (command === "enable-autostart") {
|
|
86
|
+
enableAutoStart();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (command === "disable-autostart") {
|
|
91
|
+
disableAutoStart();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (command === "status") {
|
|
96
|
+
checkAutoStartStatus();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
console.log("Available commands:");
|
|
79
|
-
console.log(" vortix login
|
|
80
|
-
console.log(" vortix start
|
|
101
|
+
console.log(" vortix login - Set device password");
|
|
102
|
+
console.log(" vortix start - Start the agent");
|
|
103
|
+
console.log(" vortix enable-autostart - Enable auto-start on system boot");
|
|
104
|
+
console.log(" vortix disable-autostart - Disable auto-start");
|
|
105
|
+
console.log(" vortix status - Check auto-start status");
|
|
81
106
|
|
|
82
107
|
function startAgent() {
|
|
83
108
|
const config = loadConfig();
|
|
@@ -92,179 +117,840 @@ function startAgent() {
|
|
|
92
117
|
const token = `${deviceToken}:${config.password}`;
|
|
93
118
|
|
|
94
119
|
console.log(`Device: ${deviceName}`);
|
|
120
|
+
console.log(`Platform: ${PLATFORM} (${IS_WINDOWS ? 'Windows' : IS_MAC ? 'macOS' : IS_LINUX ? 'Linux' : 'Unknown'})`);
|
|
95
121
|
console.log("Connecting to backend...");
|
|
96
122
|
|
|
97
|
-
//
|
|
123
|
+
// Backend URL - supports both local and production
|
|
124
|
+
// For production: set BACKEND_URL environment variable
|
|
125
|
+
// For local: defaults to ws://localhost:8080
|
|
98
126
|
const BACKEND_URL = process.env.BACKEND_URL || 'wss://vortix.onrender.com';
|
|
127
|
+
console.log(`Backend URL: ${BACKEND_URL}`);
|
|
99
128
|
|
|
100
|
-
|
|
129
|
+
let ws = null;
|
|
130
|
+
let screenCaptureInterval = null;
|
|
131
|
+
let reconnectTimeout = null;
|
|
132
|
+
let isIntentionalClose = false;
|
|
101
133
|
|
|
102
|
-
|
|
103
|
-
console.log(
|
|
134
|
+
function connect() {
|
|
135
|
+
console.log(`Attempting connection to: ${BACKEND_URL}`);
|
|
136
|
+
ws = new WebSocket(`${BACKEND_URL}?token=${encodeURIComponent(token)}`);
|
|
104
137
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
ws.send(
|
|
109
|
-
JSON.stringify({
|
|
110
|
-
type: "HEARTBEAT"
|
|
111
|
-
})
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
}, 5000);
|
|
115
|
-
});
|
|
138
|
+
ws.on("open", () => {
|
|
139
|
+
console.log("✅ Authenticated and connected to backend successfully!");
|
|
140
|
+
console.log(`Connected to: ${BACKEND_URL}`);
|
|
116
141
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
// Clear any reconnect timeout
|
|
143
|
+
if (reconnectTimeout) {
|
|
144
|
+
clearTimeout(reconnectTimeout);
|
|
145
|
+
reconnectTimeout = null;
|
|
146
|
+
}
|
|
120
147
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
148
|
+
// Send platform info immediately on connection
|
|
149
|
+
ws.send(
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
type: "HEARTBEAT",
|
|
152
|
+
platform: PLATFORM
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Then send heartbeat every 5 seconds
|
|
157
|
+
setInterval(() => {
|
|
158
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
159
|
+
console.log("Sending heartbeat...");
|
|
160
|
+
ws.send(
|
|
161
|
+
JSON.stringify({
|
|
162
|
+
type: "HEARTBEAT",
|
|
163
|
+
platform: PLATFORM
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}, 5000);
|
|
168
|
+
});
|
|
124
169
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
170
|
+
ws.on("close", (code, reason) => {
|
|
171
|
+
console.log("Disconnected from backend");
|
|
172
|
+
console.log(`Close code: ${code}, Reason: ${reason || 'No reason provided'}`);
|
|
128
173
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
174
|
+
// Clean up screen capture
|
|
175
|
+
if (screenCaptureInterval) {
|
|
176
|
+
clearInterval(screenCaptureInterval);
|
|
177
|
+
screenCaptureInterval = null;
|
|
178
|
+
}
|
|
132
179
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
180
|
+
// Auto-reconnect unless it was intentional
|
|
181
|
+
if (!isIntentionalClose) {
|
|
182
|
+
console.log("Attempting to reconnect in 5 seconds...");
|
|
183
|
+
reconnectTimeout = setTimeout(() => {
|
|
184
|
+
console.log("Reconnecting...");
|
|
185
|
+
connect();
|
|
186
|
+
}, 5000);
|
|
137
187
|
}
|
|
188
|
+
});
|
|
138
189
|
|
|
139
|
-
|
|
140
|
-
console.error("
|
|
141
|
-
|
|
142
|
-
|
|
190
|
+
ws.on("error", (err) => {
|
|
191
|
+
console.error("=".repeat(50));
|
|
192
|
+
console.error("❌ WebSocket Connection Error!");
|
|
193
|
+
console.error("Error:", err.message);
|
|
194
|
+
console.error("Trying to connect to:", BACKEND_URL);
|
|
195
|
+
console.error("=".repeat(50));
|
|
196
|
+
console.error("Troubleshooting:");
|
|
197
|
+
console.error("1. Make sure backend is running: cd backend && npm start");
|
|
198
|
+
console.error("2. Backend should show: 'Backend running on port 8080'");
|
|
199
|
+
console.error("3. Check if port 8080 is open: netstat -an | findstr :8080");
|
|
200
|
+
console.error("4. Verify BACKEND_URL environment variable (if set)");
|
|
201
|
+
console.error("=".repeat(50));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
let commandQueue = [];
|
|
205
|
+
let isRunning = false;
|
|
206
|
+
let currentCwd = require("os").homedir(); // Track current working directory
|
|
207
|
+
|
|
208
|
+
ws.on("message", (message) => {
|
|
209
|
+
try {
|
|
210
|
+
const data = JSON.parse(message.toString());
|
|
211
|
+
|
|
212
|
+
if (data.type === "EXECUTE") {
|
|
213
|
+
console.log("Agent received EXECUTE:", data.command);
|
|
214
|
+
commandQueue.push(data.command);
|
|
215
|
+
processQueue();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (data.type === "START_SCREEN_CAPTURE") {
|
|
219
|
+
console.log("🎥 Starting screen capture...");
|
|
220
|
+
if (!screenCaptureInterval) {
|
|
221
|
+
screenCaptureInterval = setInterval(async () => {
|
|
222
|
+
try {
|
|
223
|
+
console.log("📸 Capturing frame...");
|
|
224
|
+
const img = await screenshot({ format: 'jpg' });
|
|
225
|
+
const base64 = img.toString('base64');
|
|
226
|
+
console.log(`✅ Frame captured: ${img.length} bytes, base64: ${base64.length} chars`);
|
|
227
|
+
|
|
228
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
229
|
+
ws.send(JSON.stringify({
|
|
230
|
+
type: "SCREEN_FRAME",
|
|
231
|
+
frame: base64
|
|
232
|
+
}));
|
|
233
|
+
console.log("📤 Frame sent to backend");
|
|
234
|
+
} else {
|
|
235
|
+
console.log("⚠️ WebSocket not open, skipping frame");
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error("❌ Screen capture error:", err.message);
|
|
239
|
+
console.error(err.stack);
|
|
240
|
+
}
|
|
241
|
+
}, 1000); // Capture every 1 second
|
|
242
|
+
console.log("✅ Screen capture interval started");
|
|
243
|
+
} else {
|
|
244
|
+
console.log("⚠️ Screen capture already running");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (data.type === "STOP_SCREEN_CAPTURE") {
|
|
249
|
+
console.log("Stopping screen capture...");
|
|
250
|
+
if (screenCaptureInterval) {
|
|
251
|
+
clearInterval(screenCaptureInterval);
|
|
252
|
+
screenCaptureInterval = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (data.type === "ENABLE_AUTOSTART") {
|
|
257
|
+
console.log("Received request to enable auto-start...");
|
|
258
|
+
try {
|
|
259
|
+
enableAutoStart();
|
|
260
|
+
ws.send(JSON.stringify({
|
|
261
|
+
type: "AUTOSTART_STATUS",
|
|
262
|
+
enabled: true,
|
|
263
|
+
message: "Auto-start enabled successfully"
|
|
264
|
+
}));
|
|
265
|
+
} catch (err) {
|
|
266
|
+
ws.send(JSON.stringify({
|
|
267
|
+
type: "AUTOSTART_ERROR",
|
|
268
|
+
message: err.message
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (data.type === "DISABLE_AUTOSTART") {
|
|
274
|
+
console.log("Received request to disable auto-start...");
|
|
275
|
+
try {
|
|
276
|
+
disableAutoStart();
|
|
277
|
+
ws.send(JSON.stringify({
|
|
278
|
+
type: "AUTOSTART_STATUS",
|
|
279
|
+
enabled: false,
|
|
280
|
+
message: "Auto-start disabled successfully"
|
|
281
|
+
}));
|
|
282
|
+
} catch (err) {
|
|
283
|
+
ws.send(JSON.stringify({
|
|
284
|
+
type: "AUTOSTART_ERROR",
|
|
285
|
+
message: err.message
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (data.type === "GET_AUTOSTART_STATUS") {
|
|
291
|
+
const config = loadConfig();
|
|
292
|
+
ws.send(JSON.stringify({
|
|
293
|
+
type: "AUTOSTART_STATUS",
|
|
294
|
+
enabled: config.autoStart || false
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
143
297
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
298
|
+
// System stats
|
|
299
|
+
if (data.type === "GET_SYSTEM_STATS") {
|
|
300
|
+
const os = require('os');
|
|
301
|
+
const { exec } = require('child_process');
|
|
302
|
+
|
|
303
|
+
// Get accurate CPU usage (measure over 500ms for faster response)
|
|
304
|
+
const getCPUUsage = () => {
|
|
305
|
+
return new Promise((resolve) => {
|
|
306
|
+
if (IS_WINDOWS) {
|
|
307
|
+
// Windows: Use wmic for more accurate CPU reading
|
|
308
|
+
exec('wmic cpu get loadpercentage', (err, stdout) => {
|
|
309
|
+
if (err) {
|
|
310
|
+
console.error("Error getting CPU usage:", err);
|
|
311
|
+
// Fallback to Node.js method
|
|
312
|
+
fallbackCPUMeasure(resolve);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const lines = stdout.trim().split('\n');
|
|
317
|
+
if (lines.length >= 2) {
|
|
318
|
+
const cpuLoad = parseInt(lines[1].trim());
|
|
319
|
+
if (!isNaN(cpuLoad)) {
|
|
320
|
+
resolve(cpuLoad);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
fallbackCPUMeasure(resolve);
|
|
325
|
+
});
|
|
326
|
+
} else {
|
|
327
|
+
// Mac/Linux: Use Node.js method
|
|
328
|
+
fallbackCPUMeasure(resolve);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const fallbackCPUMeasure = (resolve) => {
|
|
334
|
+
const startMeasure = os.cpus();
|
|
335
|
+
|
|
336
|
+
setTimeout(() => {
|
|
337
|
+
const endMeasure = os.cpus();
|
|
338
|
+
|
|
339
|
+
let totalIdle = 0;
|
|
340
|
+
let totalTick = 0;
|
|
341
|
+
|
|
342
|
+
for (let i = 0; i < startMeasure.length; i++) {
|
|
343
|
+
const start = startMeasure[i].times;
|
|
344
|
+
const end = endMeasure[i].times;
|
|
345
|
+
|
|
346
|
+
const idleDiff = end.idle - start.idle;
|
|
347
|
+
const totalDiff =
|
|
348
|
+
(end.user - start.user) +
|
|
349
|
+
(end.nice - start.nice) +
|
|
350
|
+
(end.sys - start.sys) +
|
|
351
|
+
(end.idle - start.idle) +
|
|
352
|
+
(end.irq - start.irq);
|
|
353
|
+
|
|
354
|
+
totalIdle += idleDiff;
|
|
355
|
+
totalTick += totalDiff;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const cpuUsage = 100 - (100 * totalIdle / totalTick);
|
|
359
|
+
resolve(Math.round(cpuUsage));
|
|
360
|
+
}, 500);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Get accurate disk usage
|
|
364
|
+
const getDiskUsage = () => {
|
|
365
|
+
return new Promise((resolve) => {
|
|
366
|
+
if (IS_WINDOWS) {
|
|
367
|
+
// Windows: Get C: drive usage
|
|
368
|
+
exec('wmic logicaldisk where "DeviceID=\'C:\'" get Size,FreeSpace', (err, stdout) => {
|
|
369
|
+
if (err) {
|
|
370
|
+
console.error("Error getting disk usage:", err);
|
|
371
|
+
resolve(0);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const lines = stdout.trim().split('\n');
|
|
376
|
+
if (lines.length >= 2) {
|
|
377
|
+
const values = lines[1].trim().split(/\s+/);
|
|
378
|
+
if (values.length >= 2) {
|
|
379
|
+
const freeSpace = parseInt(values[0]);
|
|
380
|
+
const totalSpace = parseInt(values[1]);
|
|
381
|
+
const usedSpace = totalSpace - freeSpace;
|
|
382
|
+
const diskUsage = (usedSpace / totalSpace) * 100;
|
|
383
|
+
resolve(Math.round(diskUsage));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
resolve(0);
|
|
388
|
+
});
|
|
389
|
+
} else if (IS_MAC || IS_LINUX) {
|
|
390
|
+
// Mac/Linux: Get root partition usage
|
|
391
|
+
exec('df -h / | tail -1', (err, stdout) => {
|
|
392
|
+
if (err) {
|
|
393
|
+
console.error("Error getting disk usage:", err);
|
|
394
|
+
resolve(0);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const parts = stdout.trim().split(/\s+/);
|
|
399
|
+
if (parts.length >= 5) {
|
|
400
|
+
const usagePercent = parts[4].replace('%', '');
|
|
401
|
+
resolve(parseInt(usagePercent));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
resolve(0);
|
|
405
|
+
});
|
|
406
|
+
} else {
|
|
407
|
+
resolve(0);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// Get all stats
|
|
413
|
+
Promise.all([
|
|
414
|
+
getCPUUsage(),
|
|
415
|
+
getDiskUsage()
|
|
416
|
+
]).then(([cpuUsage, diskUsage]) => {
|
|
417
|
+
const totalMem = os.totalmem();
|
|
418
|
+
const freeMem = os.freemem();
|
|
419
|
+
const memoryUsage = ((totalMem - freeMem) / totalMem) * 100;
|
|
420
|
+
|
|
421
|
+
const statsData = {
|
|
422
|
+
cpu: cpuUsage,
|
|
423
|
+
memory: Math.round(memoryUsage),
|
|
424
|
+
disk: diskUsage
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
ws.send(JSON.stringify({
|
|
428
|
+
type: "SYSTEM_STATS",
|
|
429
|
+
stats: statsData
|
|
430
|
+
}));
|
|
431
|
+
}).catch(err => {
|
|
432
|
+
console.error("Error collecting stats:", err);
|
|
433
|
+
// Send fallback data
|
|
434
|
+
const totalMem = os.totalmem();
|
|
435
|
+
const freeMem = os.freemem();
|
|
436
|
+
const memoryUsage = ((totalMem - freeMem) / totalMem) * 100;
|
|
437
|
+
|
|
438
|
+
ws.send(JSON.stringify({
|
|
439
|
+
type: "SYSTEM_STATS",
|
|
440
|
+
stats: {
|
|
441
|
+
cpu: 0,
|
|
442
|
+
memory: Math.round(memoryUsage),
|
|
443
|
+
disk: 0
|
|
444
|
+
}
|
|
445
|
+
}));
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// File browsing
|
|
450
|
+
if (data.type === "BROWSE_FILES") {
|
|
451
|
+
const fs = require('fs');
|
|
452
|
+
const path = require('path');
|
|
453
|
+
|
|
454
|
+
let targetPath = data.path || os.homedir();
|
|
455
|
+
|
|
456
|
+
// Handle special paths
|
|
457
|
+
if (targetPath === "Desktop") {
|
|
458
|
+
targetPath = path.join(os.homedir(), "Desktop");
|
|
459
|
+
} else if (targetPath === "Downloads") {
|
|
460
|
+
targetPath = path.join(os.homedir(), "Downloads");
|
|
461
|
+
} else if (targetPath === "Documents") {
|
|
462
|
+
targetPath = path.join(os.homedir(), "Documents");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
const items = fs.readdirSync(targetPath);
|
|
467
|
+
const files = items.map(item => {
|
|
468
|
+
const fullPath = path.join(targetPath, item);
|
|
469
|
+
const stats = fs.statSync(fullPath);
|
|
470
|
+
return {
|
|
471
|
+
name: item,
|
|
472
|
+
type: stats.isDirectory() ? "directory" : "file",
|
|
473
|
+
size: stats.isFile() ? stats.size : undefined,
|
|
474
|
+
path: fullPath
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
console.log(`Agent: Sending FILE_LIST for ${targetPath} with ${files.length} items`);
|
|
479
|
+
ws.send(JSON.stringify({
|
|
480
|
+
type: "FILE_LIST",
|
|
481
|
+
files,
|
|
482
|
+
path: targetPath
|
|
483
|
+
}));
|
|
484
|
+
} catch (err) {
|
|
485
|
+
console.error("Error browsing files:", err.message);
|
|
486
|
+
ws.send(JSON.stringify({
|
|
487
|
+
type: "FILE_LIST",
|
|
488
|
+
files: [],
|
|
489
|
+
path: targetPath,
|
|
490
|
+
error: err.message
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// File upload
|
|
496
|
+
if (data.type === "UPLOAD_FILE") {
|
|
497
|
+
const fs = require('fs');
|
|
498
|
+
const path = require('path');
|
|
499
|
+
|
|
500
|
+
let targetPath = data.targetPath || os.homedir();
|
|
501
|
+
if (targetPath === "Desktop") {
|
|
502
|
+
targetPath = path.join(os.homedir(), "Desktop");
|
|
503
|
+
} else if (targetPath === "Downloads") {
|
|
504
|
+
targetPath = path.join(os.homedir(), "Downloads");
|
|
505
|
+
} else if (targetPath === "Documents") {
|
|
506
|
+
targetPath = path.join(os.homedir(), "Documents");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const filePath = path.join(targetPath, data.fileName);
|
|
510
|
+
const fileBuffer = Buffer.from(data.fileData, 'base64');
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
fs.writeFileSync(filePath, fileBuffer);
|
|
514
|
+
console.log(`Agent: File uploaded successfully: ${filePath}`);
|
|
515
|
+
ws.send(JSON.stringify({
|
|
516
|
+
type: "LOG",
|
|
517
|
+
deviceName,
|
|
518
|
+
message: `✓ File uploaded: ${data.fileName} to ${targetPath}`
|
|
519
|
+
}));
|
|
520
|
+
} catch (err) {
|
|
521
|
+
console.error("Agent: Error uploading file:", err.message);
|
|
522
|
+
ws.send(JSON.stringify({
|
|
523
|
+
type: "LOG",
|
|
524
|
+
deviceName,
|
|
525
|
+
message: `✗ Upload failed: ${err.message}`
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// File download
|
|
531
|
+
if (data.type === "DOWNLOAD_FILE") {
|
|
532
|
+
const fs = require('fs');
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
const fileBuffer = fs.readFileSync(data.filePath);
|
|
536
|
+
const base64 = fileBuffer.toString('base64');
|
|
537
|
+
const fileName = require('path').basename(data.filePath);
|
|
538
|
+
|
|
539
|
+
ws.send(JSON.stringify({
|
|
540
|
+
type: "FILE_DATA",
|
|
541
|
+
fileName,
|
|
542
|
+
fileData: base64
|
|
543
|
+
}));
|
|
544
|
+
|
|
545
|
+
console.log(`File downloaded: ${data.filePath}`);
|
|
546
|
+
} catch (err) {
|
|
547
|
+
console.error("Error downloading file:", err.message);
|
|
548
|
+
ws.send(JSON.stringify({
|
|
549
|
+
type: "LOG",
|
|
550
|
+
deviceName,
|
|
551
|
+
message: `✗ Download failed: ${err.message}`
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
} catch (err) {
|
|
557
|
+
console.error("Invalid message received:", message.toString());
|
|
156
558
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
function extractDirectoryFromCommand(command) {
|
|
562
|
+
// Check for cd commands and extract the target directory
|
|
563
|
+
// Handle: cd /d D:, cd /D D:\, cd D:\folder, etc.
|
|
160
564
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (/[:\\]/.test(path)) {
|
|
565
|
+
// Match: cd /d D: or cd /D D: or similar
|
|
566
|
+
let match = command.match(/^\s*cd\s+\/[dD]\s+([^\s]+)\s*$/i);
|
|
567
|
+
if (match) {
|
|
568
|
+
let path = match[1].trim();
|
|
569
|
+
path = expandPath(path);
|
|
570
|
+
// Ensure it ends with backslash if it's just a drive letter
|
|
168
571
|
if (/^[A-Za-z]:$/.test(path)) {
|
|
169
572
|
path = path + "\\";
|
|
170
573
|
}
|
|
171
574
|
console.log("CD detected, new path:", path);
|
|
172
575
|
return path;
|
|
173
576
|
}
|
|
577
|
+
|
|
578
|
+
// Match: cd D:\folder or cd folder
|
|
579
|
+
match = command.match(/^\s*cd\s+([^\s][^\s]*)\s*$/i);
|
|
580
|
+
if (match) {
|
|
581
|
+
let path = match[1].trim();
|
|
582
|
+
path = expandPath(path);
|
|
583
|
+
// Only treat as cd if it looks like a path (has : or starts with \)
|
|
584
|
+
if (/[:\\]/.test(path)) {
|
|
585
|
+
if (/^[A-Za-z]:$/.test(path)) {
|
|
586
|
+
path = path + "\\";
|
|
587
|
+
}
|
|
588
|
+
console.log("CD detected, new path:", path);
|
|
589
|
+
return path;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function expandPath(path) {
|
|
597
|
+
// Expand environment variables
|
|
598
|
+
if (path.includes("%USERPROFILE%")) {
|
|
599
|
+
path = path.replace(/%USERPROFILE%/gi, os.homedir());
|
|
600
|
+
}
|
|
601
|
+
if (path.includes("%HOMEPATH%")) {
|
|
602
|
+
path = path.replace(/%HOMEPATH%/gi, os.homedir());
|
|
603
|
+
}
|
|
604
|
+
if (path.includes("%HOMEDRIVE%")) {
|
|
605
|
+
const homedir = os.homedir();
|
|
606
|
+
const drive = homedir.split("\\")[0];
|
|
607
|
+
path = path.replace(/%HOMEDRIVE%/gi, drive);
|
|
608
|
+
}
|
|
609
|
+
return path;
|
|
174
610
|
}
|
|
175
611
|
|
|
176
|
-
|
|
612
|
+
function processQueue() {
|
|
613
|
+
if (isRunning || commandQueue.length === 0) return;
|
|
614
|
+
|
|
615
|
+
isRunning = true;
|
|
616
|
+
let command = commandQueue.shift();
|
|
617
|
+
|
|
618
|
+
console.log("Executing command:", command);
|
|
619
|
+
|
|
620
|
+
// Update current working directory if this is a cd command
|
|
621
|
+
const newCwd = extractDirectoryFromCommand(command);
|
|
622
|
+
if (newCwd) {
|
|
623
|
+
currentCwd = newCwd;
|
|
624
|
+
ws.send(JSON.stringify({ type: "LOG", deviceName, message: `Directory changed to: ${currentCwd}` }));
|
|
625
|
+
console.log("Directory state updated to:", currentCwd);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// report current cwd for diagnostics
|
|
629
|
+
ws.send(JSON.stringify({ type: "LOG", deviceName, message: `Working from: ${currentCwd}` }));
|
|
630
|
+
ws.send(JSON.stringify({ type: "LOG", deviceName, message: `Executing: ${command}` }));
|
|
631
|
+
|
|
632
|
+
// Platform-specific shell
|
|
633
|
+
const shell = IS_WINDOWS ? "cmd.exe" : IS_MAC || IS_LINUX ? "/bin/bash" : "sh";
|
|
634
|
+
const shellFlag = IS_WINDOWS ? "/c" : "-c";
|
|
635
|
+
|
|
636
|
+
// Use platform-appropriate shell
|
|
637
|
+
const process = exec(command, {
|
|
638
|
+
cwd: currentCwd,
|
|
639
|
+
shell: shell,
|
|
640
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
|
|
641
|
+
encoding: "utf8"
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// Track if we've received any output
|
|
645
|
+
let hasOutput = false;
|
|
646
|
+
|
|
647
|
+
process.stdout.on("data", (data) => {
|
|
648
|
+
hasOutput = true;
|
|
649
|
+
ws.send(JSON.stringify({
|
|
650
|
+
type: "LOG",
|
|
651
|
+
deviceName,
|
|
652
|
+
message: data.toString()
|
|
653
|
+
}));
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
process.stderr.on("data", (data) => {
|
|
657
|
+
hasOutput = true;
|
|
658
|
+
ws.send(JSON.stringify({
|
|
659
|
+
type: "LOG",
|
|
660
|
+
deviceName,
|
|
661
|
+
message: `[ERROR] ${data.toString()}`
|
|
662
|
+
}));
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
process.on("error", (err) => {
|
|
666
|
+
ws.send(JSON.stringify({
|
|
667
|
+
type: "LOG",
|
|
668
|
+
deviceName,
|
|
669
|
+
message: `[EXEC ERROR] ${err.message}`
|
|
670
|
+
}));
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
process.on("close", (code) => {
|
|
674
|
+
ws.send(JSON.stringify({
|
|
675
|
+
type: "LOG",
|
|
676
|
+
deviceName,
|
|
677
|
+
message: `Command execution finished with code: ${code}`
|
|
678
|
+
}));
|
|
679
|
+
|
|
680
|
+
// Send structured result so server can orchestrate sequential steps
|
|
681
|
+
ws.send(JSON.stringify({
|
|
682
|
+
type: "EXECUTE_RESULT",
|
|
683
|
+
command,
|
|
684
|
+
code: typeof code === 'number' ? code : 0
|
|
685
|
+
}));
|
|
686
|
+
|
|
687
|
+
isRunning = false;
|
|
688
|
+
processQueue();
|
|
689
|
+
});
|
|
690
|
+
}
|
|
177
691
|
}
|
|
178
692
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
693
|
+
// Start the connection
|
|
694
|
+
connect();
|
|
695
|
+
|
|
696
|
+
// Handle Ctrl+C gracefully
|
|
697
|
+
process.on('SIGINT', () => {
|
|
698
|
+
console.log('\n\nShutting down gracefully...');
|
|
699
|
+
isIntentionalClose = true;
|
|
700
|
+
|
|
701
|
+
if (screenCaptureInterval) {
|
|
702
|
+
clearInterval(screenCaptureInterval);
|
|
183
703
|
}
|
|
184
|
-
|
|
185
|
-
|
|
704
|
+
|
|
705
|
+
if (reconnectTimeout) {
|
|
706
|
+
clearTimeout(reconnectTimeout);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
710
|
+
ws.close();
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
console.log('Agent stopped.');
|
|
714
|
+
process.exit(0);
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
// ========== AUTO-START FUNCTIONALITY ==========
|
|
720
|
+
|
|
721
|
+
function enableAutoStart() {
|
|
722
|
+
console.log("Enabling auto-start on system boot...");
|
|
723
|
+
|
|
724
|
+
const config = loadConfig();
|
|
725
|
+
if (!config.password) {
|
|
726
|
+
console.log("❌ Error: Please set a password first using 'vortix login'");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
try {
|
|
731
|
+
if (IS_WINDOWS) {
|
|
732
|
+
enableAutoStartWindows();
|
|
733
|
+
} else if (IS_MAC) {
|
|
734
|
+
enableAutoStartMac();
|
|
735
|
+
} else if (IS_LINUX) {
|
|
736
|
+
enableAutoStartLinux();
|
|
737
|
+
} else {
|
|
738
|
+
console.log("❌ Unsupported platform");
|
|
739
|
+
return;
|
|
186
740
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
741
|
+
|
|
742
|
+
config.autoStart = true;
|
|
743
|
+
saveConfig(config);
|
|
744
|
+
console.log("✅ Auto-start enabled successfully!");
|
|
745
|
+
console.log(" The agent will start automatically on system boot.");
|
|
746
|
+
} catch (err) {
|
|
747
|
+
console.error("❌ Failed to enable auto-start:", err.message);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function disableAutoStart() {
|
|
752
|
+
console.log("Disabling auto-start...");
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
if (IS_WINDOWS) {
|
|
756
|
+
disableAutoStartWindows();
|
|
757
|
+
} else if (IS_MAC) {
|
|
758
|
+
disableAutoStartMac();
|
|
759
|
+
} else if (IS_LINUX) {
|
|
760
|
+
disableAutoStartLinux();
|
|
761
|
+
} else {
|
|
762
|
+
console.log("❌ Unsupported platform");
|
|
763
|
+
return;
|
|
191
764
|
}
|
|
192
|
-
|
|
765
|
+
|
|
766
|
+
const config = loadConfig();
|
|
767
|
+
config.autoStart = false;
|
|
768
|
+
saveConfig(config);
|
|
769
|
+
console.log("✅ Auto-start disabled successfully!");
|
|
770
|
+
} catch (err) {
|
|
771
|
+
console.error("❌ Failed to disable auto-start:", err.message);
|
|
193
772
|
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function checkAutoStartStatus() {
|
|
776
|
+
const config = loadConfig();
|
|
777
|
+
console.log("\n=== Vortix Agent Status ===");
|
|
778
|
+
console.log(`Platform: ${PLATFORM} (${IS_WINDOWS ? 'Windows' : IS_MAC ? 'macOS' : IS_LINUX ? 'Linux' : 'Unknown'})`);
|
|
779
|
+
console.log(`Device: ${os.hostname()}`);
|
|
780
|
+
console.log(`Password Set: ${config.password ? '✅ Yes' : '❌ No'}`);
|
|
781
|
+
console.log(`Auto-start: ${config.autoStart ? '✅ Enabled' : '❌ Disabled'}`);
|
|
782
|
+
console.log("===========================\n");
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ========== WINDOWS AUTO-START ==========
|
|
786
|
+
|
|
787
|
+
function enableAutoStartWindows() {
|
|
788
|
+
const { execSync } = require('child_process');
|
|
194
789
|
|
|
195
|
-
|
|
196
|
-
|
|
790
|
+
// Get the path to node and the agent script
|
|
791
|
+
const nodePath = process.execPath;
|
|
792
|
+
const agentPath = __filename;
|
|
197
793
|
|
|
198
|
-
|
|
199
|
-
|
|
794
|
+
// Create a VBScript to run the agent silently (no console window)
|
|
795
|
+
const vbsPath = path.join(os.homedir(), 'vortix-agent.vbs');
|
|
796
|
+
const vbsContent = `Set WshShell = CreateObject("WScript.Shell")
|
|
797
|
+
WshShell.Run """${nodePath}"" ""${agentPath}"" start", 0, False`;
|
|
200
798
|
|
|
201
|
-
|
|
799
|
+
fs.writeFileSync(vbsPath, vbsContent);
|
|
202
800
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
801
|
+
// Add to Windows Task Scheduler
|
|
802
|
+
const taskName = "VortixAgent";
|
|
803
|
+
const command = `schtasks /create /tn "${taskName}" /tr "${vbsPath}" /sc onlogon /rl highest /f`;
|
|
804
|
+
|
|
805
|
+
try {
|
|
806
|
+
execSync(command, { stdio: 'ignore' });
|
|
807
|
+
console.log(" Created Windows Task Scheduler entry");
|
|
808
|
+
} catch (err) {
|
|
809
|
+
throw new Error("Failed to create scheduled task. Try running as administrator.");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function disableAutoStartWindows() {
|
|
814
|
+
const { execSync } = require('child_process');
|
|
815
|
+
|
|
816
|
+
const taskName = "VortixAgent";
|
|
817
|
+
const command = `schtasks /delete /tn "${taskName}" /f`;
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
execSync(command, { stdio: 'ignore' });
|
|
821
|
+
console.log(" Removed Windows Task Scheduler entry");
|
|
822
|
+
} catch (err) {
|
|
823
|
+
// Task might not exist, that's okay
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Remove VBScript file
|
|
827
|
+
const vbsPath = path.join(os.homedir(), 'vortix-agent.vbs');
|
|
828
|
+
if (fs.existsSync(vbsPath)) {
|
|
829
|
+
fs.unlinkSync(vbsPath);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// ========== macOS AUTO-START ==========
|
|
834
|
+
|
|
835
|
+
function enableAutoStartMac() {
|
|
836
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.vortix.agent.plist');
|
|
837
|
+
|
|
838
|
+
// Get the path to node and the agent script
|
|
839
|
+
const nodePath = process.execPath;
|
|
840
|
+
const agentPath = __filename;
|
|
841
|
+
|
|
842
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
843
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
844
|
+
<plist version="1.0">
|
|
845
|
+
<dict>
|
|
846
|
+
<key>Label</key>
|
|
847
|
+
<string>com.vortix.agent</string>
|
|
848
|
+
<key>ProgramArguments</key>
|
|
849
|
+
<array>
|
|
850
|
+
<string>${nodePath}</string>
|
|
851
|
+
<string>${agentPath}</string>
|
|
852
|
+
<string>start</string>
|
|
853
|
+
</array>
|
|
854
|
+
<key>RunAtLoad</key>
|
|
855
|
+
<true/>
|
|
856
|
+
<key>KeepAlive</key>
|
|
857
|
+
<true/>
|
|
858
|
+
<key>StandardOutPath</key>
|
|
859
|
+
<string>${os.homedir()}/Library/Logs/vortix-agent.log</string>
|
|
860
|
+
<key>StandardErrorPath</key>
|
|
861
|
+
<string>${os.homedir()}/Library/Logs/vortix-agent-error.log</string>
|
|
862
|
+
</dict>
|
|
863
|
+
</plist>`;
|
|
864
|
+
|
|
865
|
+
// Create LaunchAgents directory if it doesn't exist
|
|
866
|
+
const launchAgentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
867
|
+
if (!fs.existsSync(launchAgentsDir)) {
|
|
868
|
+
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
fs.writeFileSync(plistPath, plistContent);
|
|
872
|
+
|
|
873
|
+
// Load the launch agent
|
|
874
|
+
const { execSync } = require('child_process');
|
|
875
|
+
try {
|
|
876
|
+
execSync(`launchctl load ${plistPath}`, { stdio: 'ignore' });
|
|
877
|
+
console.log(" Created macOS LaunchAgent");
|
|
878
|
+
} catch (err) {
|
|
879
|
+
console.log(" Created LaunchAgent (will load on next login)");
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function disableAutoStartMac() {
|
|
884
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.vortix.agent.plist');
|
|
885
|
+
|
|
886
|
+
if (fs.existsSync(plistPath)) {
|
|
887
|
+
const { execSync } = require('child_process');
|
|
888
|
+
try {
|
|
889
|
+
execSync(`launchctl unload ${plistPath}`, { stdio: 'ignore' });
|
|
890
|
+
} catch (err) {
|
|
891
|
+
// Might not be loaded, that's okay
|
|
209
892
|
}
|
|
210
893
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
894
|
+
fs.unlinkSync(plistPath);
|
|
895
|
+
console.log(" Removed macOS LaunchAgent");
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// ========== LINUX AUTO-START ==========
|
|
214
900
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
cwd: currentCwd,
|
|
218
|
-
shell: "cmd.exe",
|
|
219
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
|
|
220
|
-
encoding: "utf8"
|
|
221
|
-
});
|
|
901
|
+
function enableAutoStartLinux() {
|
|
902
|
+
const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'vortix-agent.service');
|
|
222
903
|
|
|
223
|
-
|
|
224
|
-
|
|
904
|
+
// Get the path to node and the agent script
|
|
905
|
+
const nodePath = process.execPath;
|
|
906
|
+
const agentPath = __filename;
|
|
225
907
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
type: "LOG",
|
|
230
|
-
deviceName,
|
|
231
|
-
message: data.toString()
|
|
232
|
-
}));
|
|
233
|
-
});
|
|
908
|
+
const serviceContent = `[Unit]
|
|
909
|
+
Description=Vortix Agent - Remote OS Control
|
|
910
|
+
After=network.target
|
|
234
911
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
message: `[ERROR] ${data.toString()}`
|
|
241
|
-
}));
|
|
242
|
-
});
|
|
912
|
+
[Service]
|
|
913
|
+
Type=simple
|
|
914
|
+
ExecStart=${nodePath} ${agentPath} start
|
|
915
|
+
Restart=always
|
|
916
|
+
RestartSec=10
|
|
243
917
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
type: "LOG",
|
|
247
|
-
deviceName,
|
|
248
|
-
message: `[EXEC ERROR] ${err.message}`
|
|
249
|
-
}));
|
|
250
|
-
});
|
|
918
|
+
[Install]
|
|
919
|
+
WantedBy=default.target`;
|
|
251
920
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
921
|
+
// Create systemd user directory if it doesn't exist
|
|
922
|
+
const systemdDir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
923
|
+
if (!fs.existsSync(systemdDir)) {
|
|
924
|
+
fs.mkdirSync(systemdDir, { recursive: true });
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
fs.writeFileSync(servicePath, serviceContent);
|
|
928
|
+
|
|
929
|
+
// Enable and start the service
|
|
930
|
+
const { execSync } = require('child_process');
|
|
931
|
+
try {
|
|
932
|
+
execSync('systemctl --user daemon-reload', { stdio: 'ignore' });
|
|
933
|
+
execSync('systemctl --user enable vortix-agent.service', { stdio: 'ignore' });
|
|
934
|
+
execSync('systemctl --user start vortix-agent.service', { stdio: 'ignore' });
|
|
935
|
+
console.log(" Created systemd user service");
|
|
936
|
+
} catch (err) {
|
|
937
|
+
console.log(" Created service file (enable with: systemctl --user enable vortix-agent.service)");
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function disableAutoStartLinux() {
|
|
942
|
+
const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'vortix-agent.service');
|
|
943
|
+
|
|
944
|
+
if (fs.existsSync(servicePath)) {
|
|
945
|
+
const { execSync } = require('child_process');
|
|
946
|
+
try {
|
|
947
|
+
execSync('systemctl --user stop vortix-agent.service', { stdio: 'ignore' });
|
|
948
|
+
execSync('systemctl --user disable vortix-agent.service', { stdio: 'ignore' });
|
|
949
|
+
} catch (err) {
|
|
950
|
+
// Might not be running, that's okay
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
fs.unlinkSync(servicePath);
|
|
954
|
+
console.log(" Removed systemd user service");
|
|
269
955
|
}
|
|
270
956
|
}
|
package/agent/package.json
CHANGED
package/bin/vortix.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
4
6
|
|
|
5
7
|
const command = process.argv[2];
|
|
6
8
|
|
|
@@ -9,21 +11,73 @@ const packageRoot = path.join(__dirname, '..');
|
|
|
9
11
|
const agentPath = path.join(packageRoot, 'agent', 'agent.js');
|
|
10
12
|
const backendPath = path.join(packageRoot, 'backend', 'server.js');
|
|
11
13
|
|
|
14
|
+
// Check if this is first run
|
|
15
|
+
const configPath = path.join(os.homedir(), '.vortix-config.json');
|
|
16
|
+
const firstRunFlagPath = path.join(os.homedir(), '.vortix-first-run');
|
|
17
|
+
|
|
18
|
+
function showWelcome() {
|
|
19
|
+
const colors = {
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
bright: '\x1b[1m',
|
|
22
|
+
green: '\x1b[32m',
|
|
23
|
+
cyan: '\x1b[36m',
|
|
24
|
+
yellow: '\x1b[33m',
|
|
25
|
+
magenta: '\x1b[35m',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
console.log('\n');
|
|
29
|
+
console.log(colors.bright + colors.green + ' ╦ ╦┌─┐┬─┐┌┬┐┬─┐ ┬' + colors.reset);
|
|
30
|
+
console.log(colors.bright + colors.green + ' ╚╗╔╝│ │├┬┘ │ │┌┴┬┘' + colors.reset);
|
|
31
|
+
console.log(colors.bright + colors.green + ' ╚╝ └─┘┴└─ ┴ ┴┴ └─' + colors.reset);
|
|
32
|
+
console.log('\n');
|
|
33
|
+
console.log(colors.bright + colors.cyan + ' 🚀 Welcome to Vortix!' + colors.reset);
|
|
34
|
+
console.log(colors.yellow + ' AI-Powered Remote OS Control' + colors.reset);
|
|
35
|
+
console.log('\n');
|
|
36
|
+
console.log(colors.bright + ' 📖 Quick Start:' + colors.reset);
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(' ' + colors.cyan + '1.' + colors.reset + ' Set device password:');
|
|
39
|
+
console.log(' ' + colors.green + 'vortix login' + colors.reset);
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(' ' + colors.cyan + '2.' + colors.reset + ' Start the agent:');
|
|
42
|
+
console.log(' ' + colors.green + 'vortix start' + colors.reset);
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(' ' + colors.cyan + '3.' + colors.reset + ' Open dashboard:');
|
|
45
|
+
console.log(' ' + colors.magenta + colors.bright + 'https://vortixai.vercel.app' + colors.reset);
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(colors.yellow + ' ⚡ Pro Tip: ' + colors.reset + 'Use AI commands in the dashboard for natural language control!');
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
|
|
12
51
|
function showHelp() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
52
|
+
showWelcome();
|
|
53
|
+
console.log(' 📚 Available Commands:\n');
|
|
54
|
+
console.log(' vortix login Set device password');
|
|
55
|
+
console.log(' vortix start Start the agent');
|
|
56
|
+
console.log(' vortix enable-autostart Enable auto-start on system boot');
|
|
57
|
+
console.log(' vortix disable-autostart Disable auto-start');
|
|
58
|
+
console.log(' vortix status Check agent and auto-start status');
|
|
59
|
+
console.log(' vortix backend Start backend server (dev only)');
|
|
60
|
+
console.log(' vortix help Show this help message');
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log(' 📖 Documentation:');
|
|
63
|
+
console.log(' https://github.com/Vaibhav262610/vortix');
|
|
64
|
+
console.log('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Show welcome message on first run or if no command provided
|
|
68
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
69
|
+
if (!fs.existsSync(firstRunFlagPath) && !command) {
|
|
70
|
+
// First run without command
|
|
71
|
+
showWelcome();
|
|
72
|
+
fs.writeFileSync(firstRunFlagPath, Date.now().toString());
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
75
|
+
showHelp();
|
|
76
|
+
process.exit(0);
|
|
77
|
+
} else if (!command) {
|
|
78
|
+
showHelp();
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
27
81
|
}
|
|
28
82
|
|
|
29
83
|
function runAgent(cmd) {
|
|
@@ -66,14 +120,20 @@ switch (command) {
|
|
|
66
120
|
runAgent('start');
|
|
67
121
|
break;
|
|
68
122
|
|
|
69
|
-
case '
|
|
70
|
-
|
|
123
|
+
case 'enable-autostart':
|
|
124
|
+
runAgent('enable-autostart');
|
|
71
125
|
break;
|
|
72
126
|
|
|
73
|
-
case '
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
case 'disable-autostart':
|
|
128
|
+
runAgent('disable-autostart');
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case 'status':
|
|
132
|
+
runAgent('status');
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case 'backend':
|
|
136
|
+
runBackend();
|
|
77
137
|
break;
|
|
78
138
|
|
|
79
139
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vortix",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "AI-powered OS control system with remote command execution",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,17 @@
|
|
|
15
15
|
"remote-control",
|
|
16
16
|
"cli",
|
|
17
17
|
"os-control",
|
|
18
|
-
"agent"
|
|
18
|
+
"agent",
|
|
19
|
+
"nodejs",
|
|
20
|
+
"websocket",
|
|
21
|
+
"command-execution",
|
|
22
|
+
"screen-sharing",
|
|
23
|
+
"multi-platform",
|
|
24
|
+
"windows",
|
|
25
|
+
"macos",
|
|
26
|
+
"linux",
|
|
27
|
+
"typescript",
|
|
28
|
+
"nextjs"
|
|
19
29
|
],
|
|
20
30
|
"author": "Vaibhav Rajpoot <vaibhavrajpoot2626@gmail.com>",
|
|
21
31
|
"license": "MIT",
|
|
@@ -23,6 +33,11 @@
|
|
|
23
33
|
"type": "git",
|
|
24
34
|
"url": "https://github.com/Vaibhav262610/vortix.git"
|
|
25
35
|
},
|
|
36
|
+
"homepage": "https://vortixai.vercel.app",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/Vaibhav262610/vortix/issues",
|
|
39
|
+
"email": "vaibhavrajpoot2626@gmail.com"
|
|
40
|
+
},
|
|
26
41
|
"engines": {
|
|
27
42
|
"node": ">=14.0.0"
|
|
28
43
|
},
|
|
@@ -35,6 +50,7 @@
|
|
|
35
50
|
],
|
|
36
51
|
"dependencies": {
|
|
37
52
|
"axios": "^1.6.0",
|
|
38
|
-
"ws": "^8.19.0"
|
|
53
|
+
"ws": "^8.19.0",
|
|
54
|
+
"screenshot-desktop": "^1.15.0"
|
|
39
55
|
}
|
|
40
56
|
}
|