@magpiecloud/mags 1.8.2 → 1.8.3

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/bin/mags.js CHANGED
@@ -208,6 +208,7 @@ ${colors.bold}Commands:${colors.reset}
208
208
  list List recent jobs
209
209
  url <name|id> [port] Enable URL access for a job
210
210
  stop <name|id> Stop a running job
211
+ resize <workspace> --disk <GB> Resize a workspace's disk (restarts VM)
211
212
  sync <workspace|id> Sync workspace to S3 (without stopping)
212
213
  workspace list List persistent workspaces
213
214
  workspace delete <id> Delete a workspace and its S3 data
@@ -730,6 +731,61 @@ async function stopJob(nameOrId) {
730
731
  }
731
732
  }
732
733
 
734
+ async function resizeVM(args) {
735
+ let name = null;
736
+ let diskGB = 0;
737
+
738
+ for (let i = 0; i < args.length; i++) {
739
+ if (args[i] === '--disk' && args[i + 1]) {
740
+ diskGB = parseInt(args[++i]) || 0;
741
+ } else if (!name) {
742
+ name = args[i];
743
+ }
744
+ }
745
+
746
+ if (!name || !diskGB) {
747
+ log('red', 'Error: Workspace name and --disk <GB> required');
748
+ console.log('\nUsage: mags resize <workspace> --disk <GB>\n');
749
+ process.exit(1);
750
+ }
751
+
752
+ // Find existing job for this workspace
753
+ const existingJob = await findWorkspaceJob(name);
754
+ if (existingJob) {
755
+ // Sync workspace before stopping (preserve files)
756
+ if (existingJob.status === 'running') {
757
+ log('blue', 'Syncing workspace before resize...');
758
+ await request('POST', `/api/v1/mags-jobs/${existingJob.request_id}/sync`);
759
+ }
760
+ log('blue', `Stopping existing VM...`);
761
+ await request('POST', `/api/v1/mags-jobs/${existingJob.request_id}/stop`);
762
+ // Brief wait for the stop to complete
763
+ await new Promise(r => setTimeout(r, 1000));
764
+ }
765
+
766
+ // Create new VM with the same workspace name and new disk size
767
+ log('blue', `Creating new VM with ${diskGB}GB disk...`);
768
+ const payload = {
769
+ script: 'sleep infinity',
770
+ type: 'inline',
771
+ persistent: true,
772
+ name: name,
773
+ workspace_id: name,
774
+ startup_command: 'sleep infinity',
775
+ disk_gb: diskGB,
776
+ };
777
+
778
+ const response = await request('POST', '/api/v1/mags-jobs', payload);
779
+ if (!response.request_id) {
780
+ log('red', 'Failed to create VM:');
781
+ console.log(JSON.stringify(response, null, 2));
782
+ process.exit(1);
783
+ }
784
+
785
+ log('green', `Resized '${name}' to ${diskGB}GB disk`);
786
+ log('gray', `Job: ${response.request_id}`);
787
+ }
788
+
733
789
  async function syncWorkspace(nameOrId) {
734
790
  if (!nameOrId) {
735
791
  log('red', 'Error: Workspace name or job ID required');
@@ -1366,7 +1422,7 @@ async function main() {
1366
1422
  break;
1367
1423
  case '--version':
1368
1424
  case '-v':
1369
- console.log('mags v1.8.2');
1425
+ console.log('mags v1.8.3');
1370
1426
  process.exit(0);
1371
1427
  break;
1372
1428
  case 'new':
@@ -1405,6 +1461,10 @@ async function main() {
1405
1461
  await requireAuth();
1406
1462
  await stopJob(args[1]);
1407
1463
  break;
1464
+ case 'resize':
1465
+ await requireAuth();
1466
+ await resizeVM(args.slice(1));
1467
+ break;
1408
1468
  case 'sync':
1409
1469
  await requireAuth();
1410
1470
  await syncWorkspace(args[1]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpiecloud/mags",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -211,6 +211,30 @@ class Mags:
211
211
  request_id = self._resolve_job_id(name_or_id)
212
212
  return self._request("POST", f"/mags-jobs/{request_id}/stop")
213
213
 
214
+ def resize(
215
+ self,
216
+ workspace: str,
217
+ disk_gb: int,
218
+ *,
219
+ timeout: float = 30.0,
220
+ poll_interval: float = 1.0,
221
+ ) -> dict:
222
+ """Resize a workspace's disk. Stops the existing VM, then creates a new one.
223
+
224
+ Workspace files are preserved in S3.
225
+ Returns ``{"request_id": ..., "status": "running"}``.
226
+ """
227
+ existing = self.find_job(workspace)
228
+ if existing and existing.get("status") == "running":
229
+ self._request("POST", f"/mags-jobs/{existing['request_id']}/sync")
230
+ self._request("POST", f"/mags-jobs/{existing['request_id']}/stop")
231
+ time.sleep(1)
232
+ elif existing and existing.get("status") == "sleeping":
233
+ self._request("POST", f"/mags-jobs/{existing['request_id']}/stop")
234
+ time.sleep(1)
235
+
236
+ return self.new(workspace, disk_gb=disk_gb, timeout=timeout, poll_interval=poll_interval)
237
+
214
238
  def new(
215
239
  self,
216
240
  name: str,