@magpiecloud/mags 1.8.11 → 1.8.13

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/README.md CHANGED
@@ -6,9 +6,9 @@ Execute scripts instantly on Magpie's microVM infrastructure. VMs boot in <100ms
6
6
 
7
7
  Mags is a CLI and SDK for running scripts on ephemeral microVMs. Each execution gets its own isolated VM that:
8
8
 
9
- - Boots in <100ms (from warm pool)
10
- - Has persistent workspaces synced to S3
11
- - Syncs /root directory automatically every 30 seconds
9
+ - Boots in ~300ms (from warm pool)
10
+ - Supports optional S3-backed persistent workspaces (with `-p` flag)
11
+ - Syncs /root directory automatically when persistence is enabled
12
12
  - Can expose public URLs for web services
13
13
  - Provides SSH access through secure proxy
14
14
  - Auto-sleeps when idle and wakes on request
@@ -51,16 +51,17 @@ export MAGS_API_TOKEN="your-token-here"
51
51
  mags run 'echo Hello World'
52
52
  ```
53
53
 
54
- ### Create a persistent VM
54
+ ### Create a sandbox
55
55
 
56
56
  ```bash
57
- # Create a new VM with workspace "myproject"
57
+ # Create a new VM (local disk only)
58
58
  mags new myproject
59
59
 
60
+ # Create with S3 data persistence
61
+ mags new myproject -p
62
+
60
63
  # SSH into it
61
64
  mags ssh myproject
62
-
63
- # Your /root directory persists to S3 automatically
64
65
  ```
65
66
 
66
67
  ### Run with a persistent workspace
@@ -91,7 +92,7 @@ mags run -p --url 'python3 -m http.server 8080'
91
92
  | `mags login` | Authenticate with Magpie (saves token locally) |
92
93
  | `mags logout` | Remove saved credentials |
93
94
  | `mags whoami` | Show current authentication status |
94
- | `mags new <workspace>` | Create a persistent VM with workspace |
95
+ | `mags new <workspace> [-p]` | Create a VM sandbox (add `-p` for S3 persistence) |
95
96
  | `mags run [options] <script>` | Execute a script on a microVM |
96
97
  | `mags ssh <workspace>` | Open interactive SSH session to a VM |
97
98
  | `mags status <job-id>` | Get job status |
@@ -230,7 +231,7 @@ If an always-on VM's host becomes unhealthy, the orchestrator automatically re-p
230
231
  ### Interactive Development
231
232
 
232
233
  ```bash
233
- # Create a persistent dev environment
234
+ # Create a dev environment (local disk)
234
235
  mags new dev-env
235
236
 
236
237
  # SSH in and work
@@ -263,7 +264,7 @@ const mags = new Mags({
263
264
  |--------|-------------|
264
265
  | `run(script, options)` | Submit a job. Options: `name`, `workspaceId`, `baseWorkspaceId`, `persistent`, `noSleep`, `ephemeral`, `startupCommand`, `environment`, `fileIds`, `diskGb` |
265
266
  | `runAndWait(script, options)` | Submit and block until done. Extra options: `timeout`, `pollInterval` |
266
- | `new(name, options)` | Create a persistent VM and wait until running. Options: `baseWorkspaceId`, `diskGb`, `timeout` |
267
+ | `new(name, options)` | Create a VM sandbox and wait until running. Options: `persistent`, `baseWorkspaceId`, `diskGb`, `timeout` |
267
268
  | `status(requestId)` | Get job status |
268
269
  | `logs(requestId)` | Get job logs |
269
270
  | `list({page, pageSize})` | List recent jobs (paginated) |
@@ -315,9 +316,12 @@ const mags = new Mags({
315
316
  const result = await mags.runAndWait('echo Hello World');
316
317
  console.log(result.logs);
317
318
 
318
- // Create a persistent VM
319
+ // Create a sandbox (local disk)
319
320
  await mags.new('myproject');
320
321
 
322
+ // Create a sandbox with S3 persistence
323
+ await mags.new('myproject', { persistent: true });
324
+
321
325
  // Execute command on existing VM
322
326
  const { output } = await mags.exec('myproject', 'ls -la /root');
323
327
  console.log(output);
@@ -415,7 +419,7 @@ mags run 'apk add ffmpeg imagemagick'
415
419
 
416
420
  | Metric | Value |
417
421
  |--------|-------|
418
- | Warm start | <100ms |
422
+ | Warm start | ~300ms |
419
423
  | Cold start | ~4 seconds |
420
424
  | Script overhead | ~50ms |
421
425
  | Workspace sync | Every 30 seconds |
package/bin/mags.js CHANGED
@@ -246,7 +246,8 @@ ${colors.bold}Cron Options:${colors.reset}
246
246
 
247
247
  ${colors.bold}Examples:${colors.reset}
248
248
  mags login
249
- mags new myvm # Create VM, get ID
249
+ mags new myvm # Create VM (local disk only)
250
+ mags new myvm -p # Create VM with S3 persistence
250
251
  mags ssh myvm # SSH (auto-starts if needed)
251
252
  mags exec myvm 'ls -la' # Run command on existing VM
252
253
  mags run 'echo Hello World'
@@ -407,9 +408,12 @@ async function newVM(args) {
407
408
  let name = null;
408
409
  let baseWorkspace = null;
409
410
  let diskGB = 0;
411
+ let persistent = false;
410
412
 
411
413
  for (let i = 0; i < args.length; i++) {
412
- if (args[i] === '--base' && args[i + 1]) {
414
+ if (args[i] === '-p' || args[i] === '--persistent') {
415
+ persistent = true;
416
+ } else if (args[i] === '--base' && args[i + 1]) {
413
417
  baseWorkspace = args[++i];
414
418
  } else if (args[i] === '--disk' && args[i + 1]) {
415
419
  diskGB = parseInt(args[++i]) || 0;
@@ -420,17 +424,36 @@ async function newVM(args) {
420
424
 
421
425
  if (!name) {
422
426
  log('red', 'Error: Name required');
423
- console.log(`\nUsage: mags new <name> [--base <workspace>] [--disk <GB>]\n`);
427
+ console.log(`\nUsage: mags new <name> [-p] [--base <workspace>] [--disk <GB>]`);
428
+ console.log(` -p, --persistent Enable S3 data persistence\n`);
424
429
  process.exit(1);
425
430
  }
426
431
 
432
+ // Check if a VM with this name already exists (running or sleeping)
433
+ try {
434
+ const jobs = await request('GET', '/api/v1/mags-jobs');
435
+ const existing = (jobs.jobs || []).find(j =>
436
+ j.name === name && (j.status === 'running' || j.status === 'sleeping')
437
+ );
438
+ if (existing) {
439
+ log('yellow', `Sandbox '${name}' already exists (status: ${existing.status})`);
440
+ console.log(` SSH: mags ssh ${name}`);
441
+ console.log(` Exec: mags exec ${name} <command>`);
442
+ console.log(` Stop: mags stop ${name}`);
443
+ process.exit(0);
444
+ }
445
+ } catch (e) {
446
+ // Non-fatal — proceed with creation if list fails
447
+ }
448
+
427
449
  const payload = {
428
450
  script: 'sleep infinity',
429
451
  type: 'inline',
430
452
  persistent: true,
431
453
  name: name,
432
454
  workspace_id: name,
433
- startup_command: 'sleep infinity'
455
+ startup_command: 'sleep infinity',
456
+ no_sync: !persistent
434
457
  };
435
458
  if (baseWorkspace) payload.base_workspace_id = baseWorkspace;
436
459
  if (diskGB) payload.disk_gb = diskGB;
@@ -451,9 +474,12 @@ async function newVM(args) {
451
474
  const status = await request('GET', `/api/v1/mags-jobs/${response.request_id}/status`);
452
475
 
453
476
  if (status.status === 'running') {
454
- log('green', `VM '${name}' created successfully`);
477
+ log('green', `VM '${name}' created successfully${persistent ? ' (persistent)' : ' (local disk)'}`);
455
478
  console.log(` ID: ${response.request_id}`);
456
479
  console.log(` SSH: mags ssh ${name}`);
480
+ if (!persistent) {
481
+ log('gray', ` Data is on local disk only. Use -p flag for S3 persistence.`);
482
+ }
457
483
  return;
458
484
  } else if (status.status === 'error') {
459
485
  log('red', `VM creation failed: ${status.error_message || 'Unknown error'}`);
package/index.js CHANGED
@@ -123,6 +123,7 @@ class Mags {
123
123
  };
124
124
 
125
125
  if (options.noSleep) payload.no_sleep = true;
126
+ if (options.noSync) payload.no_sync = true;
126
127
  if (options.name) payload.name = options.name;
127
128
  if (!options.ephemeral && options.workspaceId) payload.workspace_id = options.workspaceId;
128
129
  if (options.baseWorkspaceId) payload.base_workspace_id = options.baseWorkspaceId;
@@ -262,9 +263,12 @@ class Mags {
262
263
  }
263
264
 
264
265
  /**
265
- * Create a new persistent VM workspace and wait until it's running
266
+ * Create a new VM sandbox and wait until it's running.
267
+ * By default, data lives on local disk only (no S3 sync).
268
+ * Pass persistent: true to enable S3 data persistence.
266
269
  * @param {string} name - Workspace name
267
270
  * @param {object} options - Options
271
+ * @param {boolean} options.persistent - Enable S3 data persistence (default: false)
268
272
  * @param {string} options.baseWorkspaceId - Read-only base workspace to mount
269
273
  * @param {number} options.diskGb - Custom disk size in GB
270
274
  * @param {number} options.timeout - Timeout in ms (default: 30000)
@@ -278,6 +282,7 @@ class Mags {
278
282
  const result = await this.run('sleep infinity', {
279
283
  workspaceId: name,
280
284
  persistent: true,
285
+ noSync: !options.persistent,
281
286
  baseWorkspaceId: options.baseWorkspaceId,
282
287
  diskGb: options.diskGb,
283
288
  });
@@ -419,7 +424,7 @@ class Mags {
419
424
  await new Promise(resolve => setTimeout(resolve, 1000));
420
425
  }
421
426
 
422
- return this.new(workspace, { diskGb, timeout: options.timeout, pollInterval: options.pollInterval });
427
+ return this.new(workspace, { persistent: true, diskGb, timeout: options.timeout, pollInterval: options.pollInterval });
423
428
  }
424
429
 
425
430
  /**
package/nodejs/README.md CHANGED
@@ -18,10 +18,11 @@ mags login
18
18
 
19
19
  This will open your browser to create an API token. Paste the token when prompted, and it will be saved for future use.
20
20
 
21
- ### 2. Create a persistent VM
21
+ ### 2. Create a sandbox
22
22
 
23
23
  ```bash
24
- mags new myproject
24
+ mags new myproject # Local disk only
25
+ mags new myproject -p # With S3 persistence
25
26
  mags ssh myproject
26
27
  ```
27
28
 
@@ -59,9 +60,12 @@ export MAGS_API_TOKEN="your-token-here"
59
60
  ## CLI Commands
60
61
 
61
62
  ```bash
62
- # Create a persistent VM
63
+ # Create a sandbox (local disk)
63
64
  mags new myproject
64
65
 
66
+ # Create with S3 persistence
67
+ mags new myproject -p
68
+
65
69
  # SSH into it
66
70
  mags ssh myproject
67
71
 
@@ -135,11 +139,13 @@ Features:
135
139
 
136
140
  ## Workspaces & Persistence
137
141
 
138
- Your `/root` directory automatically syncs to S3:
142
+ When using persistent mode (`-p`), your `/root` directory syncs to S3:
139
143
  - **Auto-sync**: Every 30 seconds while running
140
144
  - **On stop**: Full sync before VM terminates
141
145
  - **On wake**: Previous state restored
142
146
 
147
+ Without `-p`, data lives on local disk only and is cleaned up when the VM is destroyed.
148
+
143
149
  ## Node.js SDK
144
150
 
145
151
  ```javascript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpiecloud/mags",
3
- "version": "1.8.11",
3
+ "version": "1.8.13",
4
4
  "description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
5
5
  "main": "index.js",
6
6
  "bin": {
package/python/README.md CHANGED
@@ -131,19 +131,23 @@ print(f"Jobs: {usage['total_jobs']}, VM seconds: {usage['vm_seconds']:.0f}")
131
131
  |--------|-------------|
132
132
  | `run(script, **opts)` | Submit a job (`persistent`, `no_sleep`, `workspace_id`, ...) |
133
133
  | `run_and_wait(script, **opts)` | Submit and block until done |
134
- | `new(name, **opts)` | Create a persistent VM workspace |
134
+ | `new(name, **opts)` | Create a VM sandbox (`persistent=True` for S3) |
135
135
  | `find_job(name_or_id)` | Find a running/sleeping job by name or workspace |
136
136
  | `exec(name_or_id, command)` | Run a command on an existing VM via SSH |
137
137
  | `stop(name_or_id)` | Stop a running job |
138
+ | `url(name_or_id, port)` | Enable public URL and return full URL |
138
139
  | `resize(workspace, disk_gb)` | Resize a workspace's disk |
139
140
  | `status(request_id)` | Get job status |
140
141
  | `logs(request_id)` | Get job logs |
141
142
  | `list_jobs(page, page_size)` | List recent jobs |
142
- | `update_job(request_id, startup_command)` | Update job config |
143
+ | `update_job(request_id, **opts)` | Update job config (`startup_command`, `no_sleep`) |
143
144
  | `enable_access(request_id, port)` | Enable URL or SSH access |
144
145
  | `usage(window_days)` | Get usage summary |
145
146
  | `upload_file(path)` | Upload a file, returns file ID |
146
147
  | `upload_files(paths)` | Upload multiple files |
148
+ | `url_alias_create(sub, ws_id)` | Create a stable URL alias |
149
+ | `url_alias_list()` | List URL aliases |
150
+ | `url_alias_delete(sub)` | Delete a URL alias |
147
151
  | `cron_create(**opts)` | Create a cron job |
148
152
  | `cron_list()` | List cron jobs |
149
153
  | `cron_get(id)` | Get a cron job |
@@ -154,4 +158,4 @@ print(f"Jobs: {usage['total_jobs']}, VM seconds: {usage['vm_seconds']:.0f}")
154
158
 
155
159
  - Website: [mags.run](https://mags.run)
156
160
  - Node.js SDK: `npm install @magpiecloud/mags`
157
- - CLI: `go install` or download from releases
161
+ - CLI: `npm install -g @magpiecloud/mags`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "magpie-mags"
7
- version = "1.3.4"
7
+ version = "1.3.6"
8
8
  description = "Mags SDK - Execute scripts on Magpie's instant VM infrastructure"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: magpie-mags
3
- Version: 1.3.4
3
+ Version: 1.3.5
4
4
  Summary: Mags SDK - Execute scripts on Magpie's instant VM infrastructure
5
5
  Author: Magpie Cloud
6
6
  License: MIT
@@ -156,7 +156,7 @@ print(f"Jobs: {usage['total_jobs']}, VM seconds: {usage['vm_seconds']:.0f}")
156
156
  |--------|-------------|
157
157
  | `run(script, **opts)` | Submit a job (`persistent`, `no_sleep`, `workspace_id`, ...) |
158
158
  | `run_and_wait(script, **opts)` | Submit and block until done |
159
- | `new(name, **opts)` | Create a persistent VM workspace |
159
+ | `new(name, **opts)` | Create a VM sandbox (`persistent=True` for S3) |
160
160
  | `find_job(name_or_id)` | Find a running/sleeping job by name or workspace |
161
161
  | `exec(name_or_id, command)` | Run a command on an existing VM via SSH |
162
162
  | `stop(name_or_id)` | Stop a running job |
@@ -106,6 +106,7 @@ class Mags:
106
106
  environment: Dict[str, str] | None = None,
107
107
  file_ids: List[str] | None = None,
108
108
  disk_gb: int | None = None,
109
+ no_sync: bool = False,
109
110
  ) -> dict:
110
111
  """Submit a job for execution.
111
112
 
@@ -122,6 +123,7 @@ class Mags:
122
123
  environment: Key-value env vars injected into the VM.
123
124
  file_ids: File IDs to download into VM before script runs.
124
125
  disk_gb: Custom disk size in GB (default 2GB).
126
+ no_sync: Skip S3 sync, use local disk only.
125
127
 
126
128
  Returns ``{"request_id": ..., "status": "accepted"}``.
127
129
  """
@@ -139,6 +141,8 @@ class Mags:
139
141
  }
140
142
  if no_sleep:
141
143
  payload["no_sleep"] = True
144
+ if no_sync:
145
+ payload["no_sync"] = True
142
146
  if name:
143
147
  payload["name"] = name
144
148
  if not ephemeral and workspace_id:
@@ -262,20 +266,25 @@ class Mags:
262
266
  self._request("POST", f"/mags-jobs/{existing['request_id']}/stop")
263
267
  time.sleep(1)
264
268
 
265
- return self.new(workspace, disk_gb=disk_gb, timeout=timeout, poll_interval=poll_interval)
269
+ return self.new(workspace, persistent=True, disk_gb=disk_gb,
270
+ timeout=timeout, poll_interval=poll_interval)
266
271
 
267
272
  def new(
268
273
  self,
269
274
  name: str,
270
275
  *,
276
+ persistent: bool = False,
271
277
  base_workspace_id: str | None = None,
272
278
  disk_gb: int | None = None,
273
279
  timeout: float = 30.0,
274
280
  poll_interval: float = 1.0,
275
281
  ) -> dict:
276
- """Create a new persistent VM workspace and wait until it's running.
282
+ """Create a new VM sandbox and wait until it's running.
283
+
284
+ By default, data lives on local disk only (no S3 sync).
285
+ Pass ``persistent=True`` to enable S3 data persistence.
277
286
 
278
- Equivalent to ``mags new <name>``.
287
+ Equivalent to ``mags new <name>`` or ``mags new <name> -p``.
279
288
 
280
289
  Returns ``{"request_id": ..., "status": "running"}``.
281
290
  """
@@ -283,6 +292,7 @@ class Mags:
283
292
  "sleep infinity",
284
293
  workspace_id=name,
285
294
  persistent=True,
295
+ no_sync=not persistent,
286
296
  base_workspace_id=base_workspace_id,
287
297
  disk_gb=disk_gb,
288
298
  )
package/website/api.html CHANGED
@@ -10,7 +10,7 @@
10
10
  />
11
11
  <meta name="api-base" content="https://api.magpiecloud.com" />
12
12
  <meta name="auth-base" content="https://api.magpiecloud.com" />
13
- <link rel="stylesheet" href="styles.css?v=6" />
13
+ <link rel="stylesheet" href="styles.css?v=7" />
14
14
  <script src="env.js"></script>
15
15
  <style>
16
16
  .endpoint { margin-bottom: 2.5rem; }
@@ -252,7 +252,7 @@ m = Mags(api_token="your-token")
252
252
  <tr>
253
253
  <td><code>"my-ws"</code></td>
254
254
  <td><em>omit</em></td>
255
- <td>Read+write workspace. Changes persist across runs.</td>
255
+ <td>Local workspace. Data stays on the VM only (not synced to S3). Add <code>persistent: true</code> to sync to S3.</td>
256
256
  </tr>
257
257
  <tr>
258
258
  <td><em>omit</em></td>
@@ -283,11 +283,14 @@ m = Mags(api_token="your-token")
283
283
  <a href="#get-status">Get Status</a>
284
284
  <a href="#get-logs">Get Logs</a>
285
285
  <a href="#list-jobs">List Jobs</a>
286
+ <a href="#stop-job">Stop Job</a>
286
287
  <a href="#enable-access">Enable Access</a>
287
288
  <a href="#update-job">Update Job</a>
289
+ <a href="#sync-workspace">Sync Workspace</a>
288
290
  <a href="#upload-file">Upload File</a>
289
291
  <a href="#list-workspaces">List Workspaces</a>
290
292
  <a href="#delete-workspace">Delete Workspace</a>
293
+ <a href="#url-aliases">URL Aliases</a>
291
294
  <a href="#cron-endpoints">Cron Jobs</a>
292
295
  </div>
293
296
 
@@ -322,7 +325,7 @@ m = Mags(api_token="your-token")
322
325
  <td><code>workspace_id</code></td>
323
326
  <td>string</td>
324
327
  <td>no</td>
325
- <td>Persistent workspace name. Filesystem changes are synced to S3 and restored on next run. Omit for ephemeral.</td>
328
+ <td>Workspace name. With <code>persistent: false</code>, data stays local to the VM only. With <code>persistent: true</code>, filesystem changes are synced to S3 and restored on next run. Omit for ephemeral.</td>
326
329
  </tr>
327
330
  <tr>
328
331
  <td><code>base_workspace_id</code></td>
@@ -334,7 +337,7 @@ m = Mags(api_token="your-token")
334
337
  <td><code>persistent</code></td>
335
338
  <td>bool</td>
336
339
  <td>no</td>
337
- <td>If <code>true</code>, VM stays alive after script finishes.</td>
340
+ <td>If <code>true</code>, VM stays alive after script finishes and workspace is synced to S3. Required for cloud persistence.</td>
338
341
  </tr>
339
342
  <tr>
340
343
  <td><code>no_sleep</code></td>
@@ -542,12 +545,48 @@ for job in resp["jobs"]:
542
545
  print(job["request_id"], job["status"])</code></pre>
543
546
  </div>
544
547
  <div class="tab-content" data-tab="lj-js">
545
- <pre><code>const resp = await mags.listJobs({ page: 1, pageSize: 10 });
548
+ <pre><code>const resp = await mags.list({ page: 1, pageSize: 10 });
546
549
  resp.jobs.forEach(j => console.log(j.requestId, j.status));</code></pre>
547
550
  </div>
548
551
  </div>
549
552
  </div>
550
553
 
554
+ <!-- Stop Job -->
555
+ <div class="endpoint card wide" id="stop-job" data-reveal>
556
+ <h3>Stop Job</h3>
557
+ <div class="method-url">
558
+ <span class="method post">POST</span>
559
+ <span class="url-path">/api/v1/mags-jobs/:id/stop</span>
560
+ </div>
561
+ <p>Stop a running or sleeping job. The VM is terminated and the workspace is synced to S3 if persistent.</p>
562
+
563
+ <p class="response-label">Response (200)</p>
564
+ <pre><code>{ "success": true, "message": "Job stopped" }</code></pre>
565
+
566
+ <div class="code-tabs tab-group">
567
+ <p class="response-label">SDK examples</p>
568
+ <div class="tab-bar">
569
+ <button class="tab active" data-tab="sj2-curl">curl</button>
570
+ <button class="tab" data-tab="sj2-py">Python</button>
571
+ <button class="tab" data-tab="sj2-js">Node.js</button>
572
+ <button class="tab" data-tab="sj2-cli">CLI</button>
573
+ </div>
574
+ <div class="tab-content active" data-tab="sj2-curl">
575
+ <pre><code>curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs/$ID/stop \
576
+ -H "Authorization: Bearer $TOKEN"</code></pre>
577
+ </div>
578
+ <div class="tab-content" data-tab="sj2-py">
579
+ <pre><code>m.stop("myproject") # accepts name, workspace ID, or request ID</code></pre>
580
+ </div>
581
+ <div class="tab-content" data-tab="sj2-js">
582
+ <pre><code>await mags.stop('myproject'); // accepts name, workspace ID, or request ID</code></pre>
583
+ </div>
584
+ <div class="tab-content" data-tab="sj2-cli">
585
+ <pre><code>mags stop myproject</code></pre>
586
+ </div>
587
+ </div>
588
+ </div>
589
+
551
590
  <!-- Enable Access -->
552
591
  <div class="endpoint card wide" id="enable-access" data-reveal>
553
592
  <h3>Enable Access (SSH or HTTP)</h3>
@@ -601,11 +640,11 @@ print(access.get("url"))</code></pre>
601
640
  </div>
602
641
  <div class="tab-content" data-tab="ea-js">
603
642
  <pre><code>// SSH access
604
- const ssh = await mags.enableAccess(requestId, { port: 22 });
643
+ const ssh = await mags.enableAccess(requestId, 22);
605
644
  console.log(`ssh root@${ssh.sshHost} -p ${ssh.sshPort}`);
606
645
 
607
646
  // HTTP access
608
- const access = await mags.enableAccess(requestId, { port: 8080 });</code></pre>
647
+ const access = await mags.enableAccess(requestId, 8080);</code></pre>
609
648
  </div>
610
649
  </div>
611
650
  </div>
@@ -714,6 +753,37 @@ file_ids = m.upload_files(["script.py", "data.csv"])</code></pre>
714
753
  </div>
715
754
  </div>
716
755
 
756
+ <!-- Sync Workspace -->
757
+ <div class="endpoint card wide" id="sync-workspace" data-reveal>
758
+ <h3>Sync Workspace</h3>
759
+ <div class="method-url">
760
+ <span class="method post">POST</span>
761
+ <span class="url-path">/api/v1/mags-jobs/:id/sync</span>
762
+ </div>
763
+ <p>Trigger an immediate workspace sync to S3 without stopping the VM.</p>
764
+
765
+ <p class="response-label">Response (200)</p>
766
+ <pre><code>{ "success": true, "message": "Sync initiated" }</code></pre>
767
+
768
+ <div class="code-tabs tab-group">
769
+ <p class="response-label">SDK examples</p>
770
+ <div class="tab-bar">
771
+ <button class="tab active" data-tab="sw-py">Python</button>
772
+ <button class="tab" data-tab="sw-js">Node.js</button>
773
+ <button class="tab" data-tab="sw-cli">CLI</button>
774
+ </div>
775
+ <div class="tab-content active" data-tab="sw-py">
776
+ <pre><code>m.sync(request_id)</code></pre>
777
+ </div>
778
+ <div class="tab-content" data-tab="sw-js">
779
+ <pre><code>await mags.sync(requestId);</code></pre>
780
+ </div>
781
+ <div class="tab-content" data-tab="sw-cli">
782
+ <pre><code>mags sync myproject</code></pre>
783
+ </div>
784
+ </div>
785
+ </div>
786
+
717
787
  <!-- List Workspaces -->
718
788
  <div class="endpoint card wide" id="list-workspaces" data-reveal>
719
789
  <h3>List Workspaces</h3>
@@ -794,6 +864,65 @@ mags workspace delete myproject --force # skip confirmation</code></pre>
794
864
  </div>
795
865
  </div>
796
866
 
867
+ <!-- URL Aliases -->
868
+ <div class="endpoint card wide" id="url-aliases" data-reveal>
869
+ <h3>URL Aliases</h3>
870
+ <div class="method-url">
871
+ <span class="method post">POST</span>
872
+ <span class="url-path">/api/v1/mags-url-aliases</span>
873
+ </div>
874
+ <div class="method-url" style="margin-left:0.5rem">
875
+ <span class="method get">GET</span>
876
+ <span class="url-path">/api/v1/mags-url-aliases</span>
877
+ </div>
878
+ <div class="method-url" style="margin-left:0.5rem">
879
+ <span class="method delete">DELETE</span>
880
+ <span class="url-path">/api/v1/mags-url-aliases/:subdomain</span>
881
+ </div>
882
+ <p>Create stable, human-readable URL aliases for your sandboxes. Aliases point to a workspace and automatically follow the active VM.</p>
883
+
884
+ <p class="response-label">Create request body</p>
885
+ <table class="field-table">
886
+ <thead>
887
+ <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
888
+ </thead>
889
+ <tbody>
890
+ <tr><td><code>subdomain</code></td><td>string</td><td><span class="required">YES</span></td><td>Subdomain for the alias (e.g. <code>my-api</code>).</td></tr>
891
+ <tr><td><code>workspace_id</code></td><td>string</td><td><span class="required">YES</span></td><td>Workspace to point the alias at.</td></tr>
892
+ <tr><td><code>domain</code></td><td>string</td><td>no</td><td>Custom domain (default: <code>apps.magpiecloud.com</code>).</td></tr>
893
+ </tbody>
894
+ </table>
895
+
896
+ <div class="code-tabs tab-group">
897
+ <p class="response-label">SDK examples</p>
898
+ <div class="tab-bar">
899
+ <button class="tab active" data-tab="ua-cli">CLI</button>
900
+ <button class="tab" data-tab="ua-py">Python</button>
901
+ <button class="tab" data-tab="ua-js">Node.js</button>
902
+ </div>
903
+ <div class="tab-content active" data-tab="ua-cli">
904
+ <pre><code># Create alias
905
+ mags url alias my-api myproject
906
+
907
+ # List aliases
908
+ mags url alias list
909
+
910
+ # Delete alias
911
+ mags url alias remove my-api</code></pre>
912
+ </div>
913
+ <div class="tab-content" data-tab="ua-py">
914
+ <pre><code>m.url_alias_create("my-api", "myproject")
915
+ aliases = m.url_alias_list()
916
+ m.url_alias_delete("my-api")</code></pre>
917
+ </div>
918
+ <div class="tab-content" data-tab="ua-js">
919
+ <pre><code>await mags.urlAliasCreate('my-api', 'myproject');
920
+ const aliases = await mags.urlAliasList();
921
+ await mags.urlAliasDelete('my-api');</code></pre>
922
+ </div>
923
+ </div>
924
+ </div>
925
+
797
926
  <!-- Cron Endpoints -->
798
927
  <div class="endpoint card wide" id="cron-endpoints" data-reveal>
799
928
  <h3>Cron Jobs</h3>
@@ -961,6 +1090,6 @@ mags cron remove &lt;id&gt;</code></pre>
961
1090
  </div>
962
1091
  </footer>
963
1092
 
964
- <script src="script.js?v=7"></script>
1093
+ <script src="script.js?v=8"></script>
965
1094
  </body>
966
1095
  </html>
@@ -61,7 +61,7 @@ mags ssh dev-env
61
61
 
62
62
  # inside the VM
63
63
  apk add git nodejs npm</code></pre>
64
- <p class="card-note">Create a persistent dev environment with one command.</p>
64
+ <p class="card-note">Create a dev sandbox with one command. Add <code>-p</code> for S3 persistence.</p>
65
65
  </div>
66
66
  </div>
67
67
  </section>