@magpiecloud/mags 1.1.0 → 1.2.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.
Files changed (2) hide show
  1. package/bin/mags.js +89 -2
  2. package/package.json +1 -1
package/bin/mags.js CHANGED
@@ -6,7 +6,7 @@ const { URL } = require('url');
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const readline = require('readline');
9
- const { exec } = require('child_process');
9
+ const { exec, spawn } = require('child_process');
10
10
 
11
11
  // Config file path
12
12
  const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.mags');
@@ -144,6 +144,7 @@ ${colors.bold}Commands:${colors.reset}
144
144
  logout Remove saved credentials
145
145
  whoami Show current authenticated user
146
146
  run [options] <script> Execute a script on a microVM
147
+ ssh <job-id> Open SSH session to a running VM
147
148
  status <job-id> Get job status
148
149
  logs <job-id> Get job logs
149
150
  list List recent jobs
@@ -163,6 +164,7 @@ ${colors.bold}Examples:${colors.reset}
163
164
  mags run -w myproject 'python3 script.py'
164
165
  mags run -p --url 'python3 -m http.server 8080'
165
166
  mags run -w webapp -p --url --port 3000 'npm start'
167
+ mags ssh abc123 # SSH into a running VM
166
168
  mags status abc123
167
169
  mags logs abc123
168
170
  mags url abc123 8080
@@ -492,6 +494,87 @@ async function stopJob(requestId) {
492
494
  }
493
495
  }
494
496
 
497
+ async function sshToJob(requestId) {
498
+ if (!requestId) {
499
+ log('red', 'Error: Job ID required');
500
+ console.log(`\nUsage: mags ssh <job-id>\n`);
501
+ console.log('Get job IDs with: mags list');
502
+ process.exit(1);
503
+ }
504
+
505
+ // First check job status
506
+ log('blue', 'Checking job status...');
507
+ const status = await request('GET', `/api/v1/mags-jobs/${requestId}/status`);
508
+
509
+ if (status.error) {
510
+ log('red', `Error: ${status.error}`);
511
+ process.exit(1);
512
+ }
513
+
514
+ if (status.status !== 'running' && status.status !== 'sleeping') {
515
+ log('red', `Cannot SSH to job with status: ${status.status}`);
516
+ log('gray', 'Job must be running or sleeping (persistent)');
517
+ process.exit(1);
518
+ }
519
+
520
+ // Enable SSH access (port 22)
521
+ log('blue', 'Enabling SSH access...');
522
+ const accessResp = await request('POST', `/api/v1/mags-jobs/${requestId}/access`, { port: 22 });
523
+
524
+ if (!accessResp.success) {
525
+ log('red', 'Failed to enable SSH access');
526
+ if (accessResp.error) {
527
+ log('red', accessResp.error);
528
+ }
529
+ process.exit(1);
530
+ }
531
+
532
+ const sshHost = accessResp.ssh_host;
533
+ const sshPort = accessResp.ssh_port;
534
+
535
+ if (!sshHost || !sshPort) {
536
+ log('red', 'SSH access enabled but no connection details returned');
537
+ console.log(JSON.stringify(accessResp, null, 2));
538
+ process.exit(1);
539
+ }
540
+
541
+ log('green', `Connecting to ${sshHost}:${sshPort}...`);
542
+ console.log(`${colors.gray}(Use Ctrl+D or 'exit' to disconnect)${colors.reset}\n`);
543
+
544
+ // Spawn SSH process
545
+ const sshArgs = [
546
+ '-o', 'StrictHostKeyChecking=no',
547
+ '-o', 'UserKnownHostsFile=/dev/null',
548
+ '-o', 'LogLevel=ERROR',
549
+ '-p', sshPort.toString(),
550
+ `root@${sshHost}`
551
+ ];
552
+
553
+ const ssh = spawn('ssh', sshArgs, {
554
+ stdio: 'inherit' // Inherit stdin/stdout/stderr for interactive session
555
+ });
556
+
557
+ ssh.on('error', (err) => {
558
+ if (err.code === 'ENOENT') {
559
+ log('red', 'SSH client not found. Please install OpenSSH.');
560
+ log('gray', 'On macOS/Linux: ssh is usually pre-installed');
561
+ log('gray', 'On Windows: Install OpenSSH or use WSL');
562
+ } else {
563
+ log('red', `SSH error: ${err.message}`);
564
+ }
565
+ process.exit(1);
566
+ });
567
+
568
+ ssh.on('close', (code) => {
569
+ if (code === 0) {
570
+ log('green', '\nSSH session ended');
571
+ } else {
572
+ log('yellow', `\nSSH session ended with code ${code}`);
573
+ }
574
+ process.exit(code || 0);
575
+ });
576
+ }
577
+
495
578
  async function main() {
496
579
  const args = process.argv.slice(2);
497
580
  const command = args[0];
@@ -516,13 +599,17 @@ async function main() {
516
599
  break;
517
600
  case '--version':
518
601
  case '-v':
519
- console.log('mags v1.0.0');
602
+ console.log('mags v1.2.0');
520
603
  process.exit(0);
521
604
  break;
522
605
  case 'run':
523
606
  await requireAuth();
524
607
  await runJob(args.slice(1));
525
608
  break;
609
+ case 'ssh':
610
+ await requireAuth();
611
+ await sshToJob(args[1]);
612
+ break;
526
613
  case 'url':
527
614
  await requireAuth();
528
615
  await enableUrlAccess(args[1], parseInt(args[2]) || 8080);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpiecloud/mags",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
5
5
  "main": "index.js",
6
6
  "bin": {