@magpiecloud/mags 1.7.1 → 1.7.2

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 +75 -0
  2. package/package.json +1 -1
package/bin/mags.js CHANGED
@@ -202,6 +202,7 @@ ${colors.bold}Commands:${colors.reset}
202
202
  new <name> Create a new persistent VM (returns ID only)
203
203
  run [options] <script> Execute a script on a microVM
204
204
  ssh <workspace|name|id> Open SSH session (auto-starts VM if needed)
205
+ exec <workspace> <command> Run a command on an existing VM
205
206
  status <name|id> Get job status
206
207
  logs <name|id> Get job logs
207
208
  list List recent jobs
@@ -240,6 +241,7 @@ ${colors.bold}Examples:${colors.reset}
240
241
  mags login
241
242
  mags new myvm # Create VM, get ID
242
243
  mags ssh myvm # SSH (auto-starts if needed)
244
+ mags exec myvm 'ls -la' # Run command on existing VM
243
245
  mags run 'echo Hello World'
244
246
  mags run -e 'echo fast' # Ephemeral (no S3 sync)
245
247
  mags run -f script.py 'python3 script.py' # Upload + run file
@@ -1241,6 +1243,75 @@ async function sshToJob(nameOrId) {
1241
1243
  });
1242
1244
  }
1243
1245
 
1246
+ async function execOnJob(nameOrId, command) {
1247
+ if (!nameOrId || !command) {
1248
+ log('red', 'Error: Workspace and command required');
1249
+ console.log(`\nUsage: mags exec <workspace|name|id> <command>\n`);
1250
+ console.log('Examples:');
1251
+ console.log(' mags exec myproject "ls -la"');
1252
+ console.log(' mags exec myproject "node --version"');
1253
+ process.exit(1);
1254
+ }
1255
+
1256
+ // Find a running/sleeping job for this workspace
1257
+ const existingJob = await findWorkspaceJob(nameOrId);
1258
+
1259
+ let jobID;
1260
+ if (existingJob && existingJob.status === 'running') {
1261
+ log('green', `Found running VM for '${nameOrId}'`);
1262
+ jobID = existingJob.request_id;
1263
+ } else if (existingJob && existingJob.status === 'sleeping') {
1264
+ log('yellow', `Waking sleeping VM for '${nameOrId}'...`);
1265
+ jobID = existingJob.request_id;
1266
+ } else {
1267
+ log('red', `No running or sleeping VM found for '${nameOrId}'`);
1268
+ log('gray', `Start one with: mags new ${nameOrId}`);
1269
+ process.exit(1);
1270
+ }
1271
+
1272
+ // Enable SSH access
1273
+ log('blue', 'Enabling SSH access...');
1274
+ const accessResp = await request('POST', `/api/v1/mags-jobs/${jobID}/access`, { port: 22 });
1275
+
1276
+ if (!accessResp.success || !accessResp.ssh_host || !accessResp.ssh_port) {
1277
+ log('red', 'Failed to enable SSH access');
1278
+ if (accessResp.error) log('red', accessResp.error);
1279
+ process.exit(1);
1280
+ }
1281
+
1282
+ // Write SSH key to temp file
1283
+ let keyFile = null;
1284
+ const sshArgs = [
1285
+ '-o', 'StrictHostKeyChecking=no',
1286
+ '-o', 'UserKnownHostsFile=/dev/null',
1287
+ '-o', 'LogLevel=ERROR',
1288
+ '-p', accessResp.ssh_port.toString()
1289
+ ];
1290
+
1291
+ if (accessResp.ssh_private_key) {
1292
+ keyFile = path.join(os.tmpdir(), `mags_ssh_${Date.now()}`);
1293
+ fs.writeFileSync(keyFile, accessResp.ssh_private_key, { mode: 0o600 });
1294
+ sshArgs.push('-i', keyFile);
1295
+ }
1296
+
1297
+ // Wrap command to use chroot if overlay is mounted
1298
+ const wrappedCmd = `if [ -d /overlay/bin ]; then chroot /overlay /bin/sh -c '${command.replace(/'/g, "'\\''")}'; else /bin/sh -c '${command.replace(/'/g, "'\\''")}'; fi`;
1299
+ sshArgs.push(`root@${accessResp.ssh_host}`, wrappedCmd);
1300
+
1301
+ const ssh = spawn('ssh', sshArgs, { stdio: 'inherit' });
1302
+
1303
+ ssh.on('error', (err) => {
1304
+ if (keyFile) try { fs.unlinkSync(keyFile); } catch (e) {}
1305
+ log('red', `SSH error: ${err.message}`);
1306
+ process.exit(1);
1307
+ });
1308
+
1309
+ ssh.on('close', (code) => {
1310
+ if (keyFile) try { fs.unlinkSync(keyFile); } catch (e) {}
1311
+ process.exit(code || 0);
1312
+ });
1313
+ }
1314
+
1244
1315
  async function main() {
1245
1316
  const args = process.argv.slice(2);
1246
1317
  const command = args[0];
@@ -1277,6 +1348,10 @@ async function main() {
1277
1348
  await requireAuth();
1278
1349
  await sshToJob(args[1]);
1279
1350
  break;
1351
+ case 'exec':
1352
+ await requireAuth();
1353
+ await execOnJob(args[1], args.slice(2).join(' '));
1354
+ break;
1280
1355
  case 'url':
1281
1356
  await requireAuth();
1282
1357
  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.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
5
5
  "main": "index.js",
6
6
  "bin": {