@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.
@@ -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
  </head>
16
16
  <body>
@@ -59,10 +59,9 @@
59
59
  </div>
60
60
  <div class="tab-content active" data-tab="hero-cli">
61
61
  <pre><code>mags run 'echo Hello World'
62
-
63
- mags run -w myproject 'pip install flask'
64
- mags ssh myproject</code></pre>
65
- <p class="card-note">Run a script, keep your files, then jump in with SSH.</p>
62
+ mags run -w myproject -p 'pip install flask'
63
+ mags new dev && mags ssh dev</code></pre>
64
+ <p class="card-note">Run a script, persist your files to the cloud, then jump in with SSH.</p>
66
65
  </div>
67
66
  <div class="tab-content" data-tab="hero-python">
68
67
  <pre><code>from mags import Mags
@@ -112,7 +111,7 @@ console.log(result.logs);</code></pre>
112
111
  <article class="card" data-reveal>
113
112
  <h3>3. Run</h3>
114
113
  <pre><code>mags run 'echo Hello World'</code></pre>
115
- <p>Add <code>-w myproject</code> to keep files between runs, synced to the cloud.</p>
114
+ <p>Add <code>-w myproject -p</code> to persist files to the cloud between runs.</p>
116
115
  </article>
117
116
  </div>
118
117
  </div>
@@ -186,14 +185,45 @@ mags login</code></pre>
186
185
  </div>
187
186
 
188
187
  <div class="ref-section">
189
- <h3>Commands</h3>
188
+ <h3>Running Scripts</h3>
189
+ <div class="ref-table-wrap">
190
+ <table class="ref-table">
191
+ <thead><tr><th>Command</th><th>Description</th></tr></thead>
192
+ <tbody>
193
+ <tr><td><code>mags run &lt;script&gt;</code></td><td>Run a script in a fresh sandbox. Fastest &mdash; no workspace, no persistence.</td></tr>
194
+ <tr><td><code>mags run -w &lt;name&gt; &lt;script&gt;</code></td><td>Run with a named workspace. Data stays on the VM only &mdash; deleted after 10 min idle.</td></tr>
195
+ <tr><td><code>mags run -w &lt;name&gt; -p &lt;script&gt;</code></td><td>Run with a persistent workspace. Files synced to S3 and survive across runs indefinitely.</td></tr>
196
+ <tr><td><code>mags run --url --port &lt;port&gt; &lt;script&gt;</code></td><td>Request a public HTTPS URL for your sandbox (requires <code>-p</code>).</td></tr>
197
+ <tr><td><code>mags run --no-sleep &lt;script&gt;</code></td><td>Keep sandbox running 24/7, never auto-sleep (requires <code>-p</code>).</td></tr>
198
+ <tr><td><code>mags run -e &lt;script&gt;</code></td><td>Ephemeral &mdash; no workspace at all, fastest possible execution.</td></tr>
199
+ <tr><td><code>mags run --base &lt;workspace&gt; &lt;script&gt;</code></td><td>Use an existing workspace as a read-only base image (OverlayFS).</td></tr>
200
+ <tr><td><code>mags run -f &lt;file&gt; &lt;script&gt;</code></td><td>Upload file(s) into the sandbox before running (repeatable).</td></tr>
201
+ </tbody>
202
+ </table>
203
+ </div>
204
+ </div>
205
+
206
+ <div class="ref-section">
207
+ <h3>Sandboxes</h3>
208
+ <div class="ref-table-wrap">
209
+ <table class="ref-table">
210
+ <thead><tr><th>Command</th><th>Description</th></tr></thead>
211
+ <tbody>
212
+ <tr><td><code>mags new &lt;name&gt;</code></td><td>Create a new sandbox. Workspace lives on local disk only.</td></tr>
213
+ <tr><td><code>mags new &lt;name&gt; -p</code></td><td>Create a sandbox with persistent workspace &mdash; synced to S3.</td></tr>
214
+ <tr><td><code>mags exec &lt;name&gt; &lt;cmd&gt;</code></td><td>Execute a command on an existing sandbox.</td></tr>
215
+ <tr><td><code>mags ssh &lt;name&gt;</code></td><td>SSH into a sandbox. Auto-starts if sleeping or stopped.</td></tr>
216
+ </tbody>
217
+ </table>
218
+ </div>
219
+ </div>
220
+
221
+ <div class="ref-section">
222
+ <h3>Management</h3>
190
223
  <div class="ref-table-wrap">
191
224
  <table class="ref-table">
192
225
  <thead><tr><th>Command</th><th>Description</th></tr></thead>
193
226
  <tbody>
194
- <tr><td><code>mags run &lt;script&gt;</code></td><td>Execute a script in a fresh sandbox</td></tr>
195
- <tr><td><code>mags ssh &lt;workspace&gt;</code></td><td>SSH into a sandbox (auto-starts if needed)</td></tr>
196
- <tr><td><code>mags exec &lt;workspace&gt; &lt;cmd&gt;</code></td><td>Run a command on an existing sandbox</td></tr>
197
227
  <tr><td><code>mags list</code></td><td>List recent jobs</td></tr>
198
228
  <tr><td><code>mags status &lt;id&gt;</code></td><td>Get job status</td></tr>
199
229
  <tr><td><code>mags logs &lt;id&gt;</code></td><td>Get job output</td></tr>
@@ -201,10 +231,16 @@ mags login</code></pre>
201
231
  <tr><td><code>mags set &lt;id&gt; [options]</code></td><td>Update VM settings (e.g. <code>--no-sleep</code>, <code>--sleep</code>)</td></tr>
202
232
  <tr><td><code>mags sync &lt;workspace&gt;</code></td><td>Sync workspace to the cloud now</td></tr>
203
233
  <tr><td><code>mags url &lt;id&gt; [port]</code></td><td>Enable public URL access</td></tr>
234
+ <tr><td><code>mags resize &lt;workspace&gt; --disk &lt;GB&gt;</code></td><td>Resize workspace disk</td></tr>
204
235
  <tr><td><code>mags workspace list</code></td><td>List persistent workspaces</td></tr>
205
236
  <tr><td><code>mags workspace delete &lt;id&gt;</code></td><td>Delete workspace + cloud data</td></tr>
237
+ <tr><td><code>mags url alias &lt;sub&gt; &lt;workspace&gt;</code></td><td>Create a stable URL alias</td></tr>
238
+ <tr><td><code>mags url alias list</code></td><td>List URL aliases</td></tr>
239
+ <tr><td><code>mags url alias remove &lt;sub&gt;</code></td><td>Delete a URL alias</td></tr>
206
240
  <tr><td><code>mags cron add [opts] &lt;script&gt;</code></td><td>Create a scheduled cron job</td></tr>
207
241
  <tr><td><code>mags cron list</code></td><td>List cron jobs</td></tr>
242
+ <tr><td><code>mags cron enable &lt;id&gt;</code></td><td>Enable a cron job</td></tr>
243
+ <tr><td><code>mags cron disable &lt;id&gt;</code></td><td>Disable a cron job</td></tr>
208
244
  <tr><td><code>mags cron remove &lt;id&gt;</code></td><td>Delete a cron job</td></tr>
209
245
  </tbody>
210
246
  </table>
@@ -212,20 +248,14 @@ mags login</code></pre>
212
248
  </div>
213
249
 
214
250
  <div class="ref-section">
215
- <h3>Run Flags</h3>
251
+ <h3>Additional Flags</h3>
216
252
  <div class="ref-table-wrap">
217
253
  <table class="ref-table">
218
254
  <thead><tr><th>Flag</th><th>Description</th></tr></thead>
219
255
  <tbody>
220
- <tr><td><code>-w, --workspace &lt;id&gt;</code></td><td>Keep files across runs (synced to the cloud)</td></tr>
221
- <tr><td><code>--base &lt;workspace&gt;</code></td><td>Clone a workspace as a starting point (read-only)</td></tr>
222
- <tr><td><code>-p, --persistent</code></td><td>Keep sandbox alive after script (sleeps when idle)</td></tr>
223
- <tr><td><code>--no-sleep</code></td><td>Never auto-sleep this VM (requires <code>-p</code>)</td></tr>
224
- <tr><td><code>-e, --ephemeral</code></td><td>No cloud sync &mdash; fastest execution</td></tr>
225
- <tr><td><code>-f, --file &lt;path&gt;</code></td><td>Upload file(s) into the sandbox (repeatable)</td></tr>
226
- <tr><td><code>-n, --name &lt;name&gt;</code></td><td>Name the job for easy reference</td></tr>
227
- <tr><td><code>--url</code></td><td>Enable public HTTPS URL (requires <code>-p</code>)</td></tr>
228
- <tr><td><code>--port &lt;port&gt;</code></td><td>Port to expose for URL (default: 8080)</td></tr>
256
+ <tr><td><code>-n, --name &lt;name&gt;</code></td><td>Alias for <code>-w</code></td></tr>
257
+ <tr><td><code>-e, --ephemeral</code></td><td>No workspace at all, fastest possible execution</td></tr>
258
+ <tr><td><code>--base &lt;workspace&gt;</code></td><td>Use an existing workspace as a read-only base image</td></tr>
229
259
  <tr><td><code>--disk &lt;GB&gt;</code></td><td>Custom disk size in GB (default: 2)</td></tr>
230
260
  <tr><td><code>--startup-command &lt;cmd&gt;</code></td><td>Command to run when sandbox wakes from sleep</td></tr>
231
261
  </tbody>
@@ -235,39 +265,25 @@ mags login</code></pre>
235
265
 
236
266
  <div class="ref-section">
237
267
  <h3>Examples</h3>
238
- <pre><code># Run a one-off command
239
- mags run 'echo Hello World && uname -a'
240
-
241
- # Persistent workspace &mdash; files survive across runs
242
- mags run -w myproject 'pip install flask requests'
243
- mags run -w myproject 'python3 app.py'
244
-
245
- # Base image &mdash; create a golden image, sync it, then reuse
246
- mags run -w golden -p 'apt install -y nodejs && npm install -g typescript'
247
- mags sync golden # persist to cloud
248
- mags run --base golden 'npm test' # read-only, changes discarded
249
- mags run --base golden -w fork-1 'npm test' # fork: load golden, save to fork-1
250
-
251
- # SSH into a workspace (auto-starts sandbox if needed)
252
- mags ssh myproject
253
-
254
- # Run a command on an existing sandbox
255
- mags exec myproject 'node --version'
256
-
257
- # Persistent sandbox with public URL
258
- mags run -w webapp -p --url --port 8080 \
268
+ <pre><code># Persistent workspace &mdash; install packages, then run your app
269
+ mags run -w myproject -p 'pip install flask requests'
270
+ mags run -w myproject -p 'python3 app.py'
271
+
272
+ # Golden image &mdash; create once, fork many times
273
+ mags run -w golden -p 'apk add nodejs npm && npm install -g typescript'
274
+ mags sync golden
275
+ mags run --base golden -w fork-1 -p 'npm test'
276
+
277
+ # Interactive sandbox with SSH
278
+ mags new dev -p
279
+ mags ssh dev
280
+ mags exec dev 'node --version'
281
+
282
+ # Always-on web server with public URL
283
+ mags run -w webapp -p --no-sleep --url --port 8080 \
259
284
  --startup-command 'python3 -m http.server 8080' \
260
285
  'python3 -m http.server 8080'
261
286
 
262
- # Always-on sandbox (never auto-sleeps)
263
- mags run -w worker -p --no-sleep 'python3 worker.py'
264
-
265
- # Upload files and run
266
- mags run -f script.py -f data.csv 'python3 script.py'
267
-
268
- # Ephemeral (fastest &mdash; no cloud sync)
269
- mags run -e 'uname -a && df -h'
270
-
271
287
  # Cron job
272
288
  mags cron add --name backup --schedule "0 0 * * *" \
273
289
  -w backups 'tar czf backup.tar.gz /data'</code></pre>
@@ -290,21 +306,28 @@ export MAGS_API_TOKEN="your-token"</code></pre>
290
306
  <tbody>
291
307
  <tr><td><code>run(script, **opts)</code></td><td>Submit a job (returns immediately)</td></tr>
292
308
  <tr><td><code>run_and_wait(script, **opts)</code></td><td>Submit + block until complete</td></tr>
293
- <tr><td><code>new(name)</code></td><td>Create persistent VM workspace</td></tr>
294
- <tr><td><code>exec(name, command)</code></td><td>Run command on existing sandbox</td></tr>
309
+ <tr><td><code>new(name, **opts)</code></td><td>Create VM sandbox (pass <code>persistent=True</code> for S3)</td></tr>
310
+ <tr><td><code>exec(name, command)</code></td><td>Run command on existing sandbox via SSH</td></tr>
295
311
  <tr><td><code>stop(name_or_id)</code></td><td>Stop a running job</td></tr>
296
312
  <tr><td><code>find_job(name_or_id)</code></td><td>Find job by name or workspace</td></tr>
297
313
  <tr><td><code>url(name_or_id, port)</code></td><td>Enable public URL access</td></tr>
314
+ <tr><td><code>resize(workspace, disk_gb)</code></td><td>Resize workspace disk</td></tr>
298
315
  <tr><td><code>status(request_id)</code></td><td>Get job status</td></tr>
299
316
  <tr><td><code>logs(request_id)</code></td><td>Get job logs</td></tr>
300
317
  <tr><td><code>list_jobs()</code></td><td>List recent jobs</td></tr>
318
+ <tr><td><code>update_job(request_id, **opts)</code></td><td>Update job settings (<code>no_sleep</code>, <code>startup_command</code>)</td></tr>
301
319
  <tr><td><code>enable_access(id, port)</code></td><td>Enable URL or SSH access (low-level)</td></tr>
320
+ <tr><td><code>upload_file(path)</code></td><td>Upload a file, returns file ID</td></tr>
302
321
  <tr><td><code>upload_files(paths)</code></td><td>Upload files, returns file IDs</td></tr>
303
322
  <tr><td><code>list_workspaces()</code></td><td>List persistent workspaces</td></tr>
304
323
  <tr><td><code>delete_workspace(id)</code></td><td>Delete workspace + cloud data</td></tr>
305
324
  <tr><td><code>sync(request_id)</code></td><td>Sync workspace to S3 now</td></tr>
325
+ <tr><td><code>url_alias_create(sub, ws_id)</code></td><td>Create a stable URL alias</td></tr>
326
+ <tr><td><code>url_alias_list()</code></td><td>List URL aliases</td></tr>
327
+ <tr><td><code>url_alias_delete(sub)</code></td><td>Delete a URL alias</td></tr>
306
328
  <tr><td><code>cron_create(**opts)</code></td><td>Create a cron job</td></tr>
307
329
  <tr><td><code>cron_list()</code></td><td>List cron jobs</td></tr>
330
+ <tr><td><code>cron_update(id, **opts)</code></td><td>Update a cron job</td></tr>
308
331
  <tr><td><code>cron_delete(id)</code></td><td>Delete a cron job</td></tr>
309
332
  <tr><td><code>usage(window_days)</code></td><td>Get usage stats</td></tr>
310
333
  </tbody>
@@ -318,11 +341,11 @@ export MAGS_API_TOKEN="your-token"</code></pre>
318
341
  <table class="ref-table">
319
342
  <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
320
343
  <tbody>
321
- <tr><td><code>workspace_id</code></td><td>Keep files across runs (synced to the cloud)</td></tr>
344
+ <tr><td><code>workspace_id</code></td><td>Name the workspace. Local only unless <code>persistent=True</code>.</td></tr>
345
+ <tr><td><code>persistent</code></td><td>Keep sandbox alive, sync workspace to S3. Files persist indefinitely.</td></tr>
322
346
  <tr><td><code>base_workspace_id</code></td><td>Mount a workspace read-only as base image</td></tr>
323
- <tr><td><code>persistent</code></td><td>Keep sandbox alive after script completes</td></tr>
324
347
  <tr><td><code>no_sleep</code></td><td>Never auto-sleep (requires <code>persistent=True</code>)</td></tr>
325
- <tr><td><code>ephemeral</code></td><td>No cloud sync (fastest)</td></tr>
348
+ <tr><td><code>ephemeral</code></td><td>No workspace, no sync (fastest)</td></tr>
326
349
  <tr><td><code>file_ids</code></td><td>List of uploaded file IDs to include</td></tr>
327
350
  <tr><td><code>startup_command</code></td><td>Command to run when sandbox wakes</td></tr>
328
351
  </tbody>
@@ -339,23 +362,25 @@ m = Mags() # reads MAGS_API_TOKEN from env
339
362
  result = m.run_and_wait("echo Hello World")
340
363
  print(result["status"]) # "completed"
341
364
 
342
- # Create a persistent workspace
365
+ # Local workspace (no S3 sync, good for analysis)
366
+ m.run_and_wait("python3 analyze.py", workspace_id="analysis")
367
+
368
+ # Persistent workspace (synced to S3)
369
+ m.run("pip install flask",
370
+ workspace_id="my-project", persistent=True)
371
+
372
+ # Create a sandbox (local disk)
343
373
  m.new("my-project")
344
374
 
375
+ # Create with S3 persistence
376
+ m.new("my-project", persistent=True)
377
+
345
378
  # Execute commands on existing sandbox
346
379
  result = m.exec("my-project", "ls -la /root")
347
380
  print(result["output"])
348
- m.exec("my-project", "pip install flask")
349
-
350
- # Stop a job
351
- m.stop("my-project")
352
-
353
- # Find a job by name or workspace
354
- job = m.find_job("my-project")
355
- print(job["status"]) # "running", "sleeping", etc.
356
381
 
357
382
  # Public URL
358
- m.new("webapp")
383
+ m.new("webapp", persistent=True)
359
384
  info = m.url("webapp", port=3000)
360
385
  print(info["url"]) # https://xyz.apps.magpiecloud.com
361
386
 
@@ -394,15 +419,28 @@ export MAGS_API_TOKEN="your-token"</code></pre>
394
419
  <tbody>
395
420
  <tr><td><code>run(script, opts)</code></td><td>Submit a job (returns immediately)</td></tr>
396
421
  <tr><td><code>runAndWait(script, opts)</code></td><td>Submit + block until complete</td></tr>
422
+ <tr><td><code>new(name, opts)</code></td><td>Create a VM sandbox (add <code>persistent: true</code> for S3)</td></tr>
423
+ <tr><td><code>exec(nameOrId, command)</code></td><td>Run command on existing sandbox via SSH</td></tr>
424
+ <tr><td><code>stop(nameOrId)</code></td><td>Stop a running job</td></tr>
425
+ <tr><td><code>findJob(nameOrId)</code></td><td>Find job by name or workspace</td></tr>
426
+ <tr><td><code>url(nameOrId, port)</code></td><td>Enable public URL access</td></tr>
397
427
  <tr><td><code>status(requestId)</code></td><td>Get job status</td></tr>
398
428
  <tr><td><code>logs(requestId)</code></td><td>Get job logs</td></tr>
399
- <tr><td><code>listJobs()</code></td><td>List recent jobs</td></tr>
400
- <tr><td><code>enableAccess(id, { port })</code></td><td>Enable URL or SSH access</td></tr>
401
- <tr><td><code>uploadFile(path)</code></td><td>Upload a file, returns file ID</td></tr>
429
+ <tr><td><code>list()</code></td><td>List recent jobs</td></tr>
430
+ <tr><td><code>updateJob(requestId, opts)</code></td><td>Update job settings (<code>noSleep</code>, <code>startupCommand</code>)</td></tr>
431
+ <tr><td><code>enableAccess(requestId, port)</code></td><td>Enable URL or SSH access</td></tr>
432
+ <tr><td><code>resize(workspace, diskGb)</code></td><td>Resize workspace disk</td></tr>
433
+ <tr><td><code>uploadFiles(paths)</code></td><td>Upload files, returns file IDs</td></tr>
434
+ <tr><td><code>sync(requestId)</code></td><td>Sync workspace to S3 now</td></tr>
435
+ <tr><td><code>listWorkspaces()</code></td><td>List persistent workspaces</td></tr>
436
+ <tr><td><code>deleteWorkspace(id)</code></td><td>Delete workspace + cloud data</td></tr>
437
+ <tr><td><code>urlAliasCreate(sub, wsId)</code></td><td>Create a stable URL alias</td></tr>
438
+ <tr><td><code>urlAliasList()</code></td><td>List URL aliases</td></tr>
439
+ <tr><td><code>urlAliasDelete(sub)</code></td><td>Delete a URL alias</td></tr>
402
440
  <tr><td><code>cronCreate(opts)</code></td><td>Create a cron job</td></tr>
403
441
  <tr><td><code>cronList()</code></td><td>List cron jobs</td></tr>
404
442
  <tr><td><code>cronDelete(id)</code></td><td>Delete a cron job</td></tr>
405
- <tr><td><code>usage(windowDays)</code></td><td>Get usage stats</td></tr>
443
+ <tr><td><code>usage(opts)</code></td><td>Get usage stats</td></tr>
406
444
  </tbody>
407
445
  </table>
408
446
  </div>
@@ -414,11 +452,11 @@ export MAGS_API_TOKEN="your-token"</code></pre>
414
452
  <table class="ref-table">
415
453
  <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
416
454
  <tbody>
417
- <tr><td><code>workspaceId</code></td><td>Keep files across runs (synced to the cloud)</td></tr>
455
+ <tr><td><code>workspaceId</code></td><td>Name the workspace. Local only unless <code>persistent: true</code>.</td></tr>
456
+ <tr><td><code>persistent</code></td><td>Keep sandbox alive, sync workspace to S3. Files persist indefinitely.</td></tr>
418
457
  <tr><td><code>baseWorkspaceId</code></td><td>Mount a workspace read-only as base image</td></tr>
419
- <tr><td><code>persistent</code></td><td>Keep sandbox alive after script completes</td></tr>
420
458
  <tr><td><code>noSleep</code></td><td>Never auto-sleep (requires <code>persistent: true</code>)</td></tr>
421
- <tr><td><code>ephemeral</code></td><td>No cloud sync (fastest)</td></tr>
459
+ <tr><td><code>ephemeral</code></td><td>No workspace, no sync (fastest)</td></tr>
422
460
  <tr><td><code>fileIds</code></td><td>Array of uploaded file IDs to include</td></tr>
423
461
  <tr><td><code>startupCommand</code></td><td>Command to run when sandbox wakes</td></tr>
424
462
  </tbody>
@@ -435,17 +473,23 @@ const mags = new Mags({ apiToken: process.env.MAGS_API_TOKEN });
435
473
  const result = await mags.runAndWait('echo Hello World');
436
474
  console.log(result.status); // "completed"
437
475
 
438
- // Persistent workspace
439
- await mags.runAndWait('pip install flask', { workspaceId: 'myproject' });
440
- await mags.runAndWait('python3 app.py', { workspaceId: 'myproject' });
476
+ // Local workspace (no S3 sync, good for analysis)
477
+ await mags.runAndWait('python3 analyze.py', { workspaceId: 'analysis' });
478
+
479
+ // Persistent workspace (synced to S3)
480
+ await mags.runAndWait('pip install flask', { workspaceId: 'myproject', persistent: true });
481
+ await mags.runAndWait('python3 app.py', { workspaceId: 'myproject', persistent: true });
441
482
 
442
483
  // Base image
443
484
  await mags.runAndWait('npm test', { baseWorkspaceId: 'golden' });
444
- await mags.runAndWait('npm test', { baseWorkspaceId: 'golden', workspaceId: 'fork-1' });
485
+ await mags.runAndWait('npm test', { baseWorkspaceId: 'golden', workspaceId: 'fork-1', persistent: true });
486
+
487
+ // Create a sandbox
488
+ await mags.new('dev', { persistent: true });
445
489
 
446
490
  // SSH access
447
491
  const job = await mags.run('sleep 3600', { workspaceId: 'dev', persistent: true });
448
- const ssh = await mags.enableAccess(job.requestId, { port: 22 });
492
+ const ssh = await mags.enableAccess(job.requestId, 22);
449
493
  console.log(`ssh root@${ssh.sshHost} -p ${ssh.sshPort}`);
450
494
 
451
495
  // Public URL
@@ -453,8 +497,8 @@ const webJob = await mags.run('python3 -m http.server 8080', {
453
497
  workspaceId: 'webapp', persistent: true,
454
498
  startupCommand: 'python3 -m http.server 8080',
455
499
  });
456
- const access = await mags.enableAccess(webJob.requestId, { port: 8080 });
457
- console.log(access.url);
500
+ const { url } = await mags.url('webapp', 8080);
501
+ console.log(url);
458
502
 
459
503
  // Always-on sandbox (never auto-sleeps)
460
504
  await mags.run('python3 worker.py', {
@@ -488,10 +532,10 @@ await mags.cronCreate({
488
532
  <article class="panel" data-reveal>
489
533
  <h3>Backed up to object storage</h3>
490
534
  <ul class="list">
491
- <li>Files, packages, and configs persist automatically</li>
492
- <li>Optional backup to S3-compatible object storage</li>
535
+ <li>Use <code>-w</code> for a local workspace (no cloud sync, good for throwaway analysis)</li>
536
+ <li>Add <code>-p</code> to sync to S3 &mdash; files, packages, and configs persist indefinitely</li>
493
537
  <li>Clone a workspace as a read-only base for new sandboxes</li>
494
- <li>Survives reboots, sleep, and agent restarts</li>
538
+ <li>Survives reboots, sleep, and agent restarts (with <code>-p</code>)</li>
495
539
  </ul>
496
540
  </article>
497
541
  <article class="panel" data-reveal>
@@ -569,8 +613,8 @@ await mags.run('node server.js', {
569
613
 
570
614
  m = Mags() # reads MAGS_API_TOKEN from env
571
615
 
572
- # Create a workspace, run commands on it
573
- m.new("demo")
616
+ # Create a sandbox, run commands on it
617
+ m.new("demo") # local disk; use persistent=True for S3
574
618
  result = m.exec("demo", "uname -a")
575
619
  print(result["output"])
576
620
 
@@ -583,13 +627,14 @@ print(result["status"]) # "completed"</code></pre>
583
627
  <ul class="list">
584
628
  <li><code>run(script, **opts)</code> &mdash; submit a job</li>
585
629
  <li><code>run_and_wait(script, **opts)</code> &mdash; submit + block</li>
586
- <li><code>new(name)</code> &mdash; create persistent VM</li>
630
+ <li><code>new(name, **opts)</code> &mdash; create VM sandbox</li>
587
631
  <li><code>exec(name, command)</code> &mdash; run on existing sandbox</li>
588
632
  <li><code>stop(name_or_id)</code> &mdash; stop a job</li>
589
633
  <li><code>find_job(name_or_id)</code> &mdash; find by name/workspace</li>
590
634
  <li><code>url(name_or_id, port)</code> &mdash; enable public URL</li>
635
+ <li><code>resize(workspace, disk_gb)</code> &mdash; resize disk</li>
591
636
  <li><code>status(id)</code> / <code>logs(id)</code> / <code>list_jobs()</code></li>
592
- <li><code>upload_files(paths)</code> &mdash; upload files</li>
637
+ <li><code>upload_file(path)</code> / <code>upload_files(paths)</code></li>
593
638
  <li><code>list_workspaces()</code> / <code>delete_workspace(id)</code></li>
594
639
  <li><code>sync(id)</code> &mdash; sync workspace to S3</li>
595
640
  <li><code>cron_create(**opts)</code> / <code>cron_list()</code> / <code>cron_delete(id)</code></li>
@@ -619,14 +664,17 @@ console.log(result.logs);</code></pre>
619
664
  <ul class="list">
620
665
  <li><code>run(script, opts)</code> &mdash; submit a job</li>
621
666
  <li><code>runAndWait(script, opts)</code> &mdash; submit + block</li>
622
- <li><code>status(requestId)</code> &mdash; get job status</li>
623
- <li><code>logs(requestId)</code> &mdash; get job logs</li>
624
- <li><code>listJobs()</code> &mdash; list recent jobs</li>
625
- <li><code>enableAccess(id, { port })</code> &mdash; URL or SSH</li>
626
- <li><code>uploadFile(path)</code> &mdash; upload a file</li>
627
- <li><code>cronCreate(opts)</code> &mdash; create cron</li>
628
- <li><code>cronList()</code> / <code>cronDelete(id)</code></li>
629
- <li><code>usage(windowDays)</code> &mdash; usage stats</li>
667
+ <li><code>new(name, opts)</code> &mdash; create VM sandbox</li>
668
+ <li><code>exec(nameOrId, command)</code> &mdash; run on existing sandbox</li>
669
+ <li><code>stop(nameOrId)</code> &mdash; stop a job</li>
670
+ <li><code>findJob(nameOrId)</code> &mdash; find by name/workspace</li>
671
+ <li><code>url(nameOrId, port)</code> &mdash; enable public URL</li>
672
+ <li><code>status(id)</code> / <code>logs(id)</code> / <code>list()</code></li>
673
+ <li><code>enableAccess(requestId, port)</code> &mdash; URL or SSH</li>
674
+ <li><code>uploadFiles(paths)</code> &mdash; upload files</li>
675
+ <li><code>listWorkspaces()</code> / <code>deleteWorkspace(id)</code></li>
676
+ <li><code>sync(id)</code> &mdash; sync workspace to S3</li>
677
+ <li><code>cronCreate(opts)</code> / <code>cronList()</code> / <code>cronDelete(id)</code></li>
630
678
  </ul>
631
679
  <p style="margin-top:1rem"><a class="text-link" href="https://www.npmjs.com/package/@magpiecloud/mags" rel="noreferrer">npm &rarr;</a></p>
632
680
  </article>
@@ -653,12 +701,18 @@ console.log(result.logs);</code></pre>
653
701
  <li><code>GET /mags-jobs/:id/status</code> &mdash; status</li>
654
702
  <li><code>GET /mags-jobs/:id/logs</code> &mdash; logs</li>
655
703
  <li><code>POST /mags-jobs/:id/access</code> &mdash; URL/SSH</li>
704
+ <li><code>POST /mags-jobs/:id/stop</code> &mdash; stop job</li>
705
+ <li><code>POST /mags-jobs/:id/sync</code> &mdash; sync workspace</li>
656
706
  <li><code>PATCH /mags-jobs/:id</code> &mdash; update</li>
657
707
  <li><code>POST /mags-files</code> &mdash; upload file</li>
658
708
  <li><code>GET /mags-workspaces</code> &mdash; list ws</li>
659
709
  <li><code>DELETE /mags-workspaces/:id</code> &mdash; delete ws</li>
710
+ <li><code>POST /mags-url-aliases</code> &mdash; create alias</li>
711
+ <li><code>GET /mags-url-aliases</code> &mdash; list aliases</li>
712
+ <li><code>DELETE /mags-url-aliases/:sub</code> &mdash; delete alias</li>
660
713
  <li><code>POST /mags-cron</code> &mdash; create cron</li>
661
714
  <li><code>GET /mags-cron</code> &mdash; list cron</li>
715
+ <li><code>PATCH /mags-cron/:id</code> &mdash; update cron</li>
662
716
  <li><code>DELETE /mags-cron/:id</code> &mdash; delete cron</li>
663
717
  </ul>
664
718
  <p style="margin-top:1rem"><a class="text-link" href="api.html">Full API reference &rarr;</a></p>
@@ -731,7 +785,7 @@ console.log(result.logs);</code></pre>
731
785
  </div>
732
786
  </footer>
733
787
 
734
- <script src="script.js?v=7"></script>
788
+ <script src="script.js?v=8"></script>
735
789
  <script>
736
790
  (function() {
737
791
  var token = localStorage.getItem('microvm-access-token');