@magpiecloud/mags 1.8.11 → 1.8.12

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
@@ -7,8 +7,8 @@ Execute scripts instantly on Magpie's microVM infrastructure. VMs boot in <100ms
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
9
  - Boots in <100ms (from warm pool)
10
- - Has persistent workspaces synced to S3
11
- - Syncs /root directory automatically every 30 seconds
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);
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,7 +424,8 @@ 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
 
@@ -430,7 +435,8 @@ async function newVM(args) {
430
435
  persistent: true,
431
436
  name: name,
432
437
  workspace_id: name,
433
- startup_command: 'sleep infinity'
438
+ startup_command: 'sleep infinity',
439
+ no_sync: !persistent
434
440
  };
435
441
  if (baseWorkspace) payload.base_workspace_id = baseWorkspace;
436
442
  if (diskGB) payload.disk_gb = diskGB;
@@ -451,9 +457,12 @@ async function newVM(args) {
451
457
  const status = await request('GET', `/api/v1/mags-jobs/${response.request_id}/status`);
452
458
 
453
459
  if (status.status === 'running') {
454
- log('green', `VM '${name}' created successfully`);
460
+ log('green', `VM '${name}' created successfully${persistent ? ' (persistent)' : ' (local disk)'}`);
455
461
  console.log(` ID: ${response.request_id}`);
456
462
  console.log(` SSH: mags ssh ${name}`);
463
+ if (!persistent) {
464
+ log('gray', ` Data is on local disk only. Use -p flag for S3 persistence.`);
465
+ }
457
466
  return;
458
467
  } else if (status.status === 'error') {
459
468
  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.12",
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,7 +131,7 @@ 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 |
@@ -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.5"
8
8
  description = "Mags SDK - Execute scripts on Magpie's instant VM infrastructure"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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
@@ -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>
@@ -322,7 +322,7 @@ m = Mags(api_token="your-token")
322
322
  <td><code>workspace_id</code></td>
323
323
  <td>string</td>
324
324
  <td>no</td>
325
- <td>Persistent workspace name. Filesystem changes are synced to S3 and restored on next run. Omit for ephemeral.</td>
325
+ <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
326
  </tr>
327
327
  <tr>
328
328
  <td><code>base_workspace_id</code></td>
@@ -334,7 +334,7 @@ m = Mags(api_token="your-token")
334
334
  <td><code>persistent</code></td>
335
335
  <td>bool</td>
336
336
  <td>no</td>
337
- <td>If <code>true</code>, VM stays alive after script finishes.</td>
337
+ <td>If <code>true</code>, VM stays alive after script finishes and workspace is synced to S3. Required for cloud persistence.</td>
338
338
  </tr>
339
339
  <tr>
340
340
  <td><code>no_sleep</code></td>
@@ -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>
@@ -60,9 +60,9 @@
60
60
  <div class="tab-content active" data-tab="hero-cli">
61
61
  <pre><code>mags run 'echo Hello World'
62
62
 
63
- mags run -w myproject 'pip install flask'
63
+ mags run -w myproject -p 'pip install flask'
64
64
  mags ssh myproject</code></pre>
65
- <p class="card-note">Run a script, keep your files, then jump in with SSH.</p>
65
+ <p class="card-note">Run a script, persist your files to the cloud, then jump in with SSH.</p>
66
66
  </div>
67
67
  <div class="tab-content" data-tab="hero-python">
68
68
  <pre><code>from mags import Mags
@@ -112,7 +112,7 @@ console.log(result.logs);</code></pre>
112
112
  <article class="card" data-reveal>
113
113
  <h3>3. Run</h3>
114
114
  <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>
115
+ <p>Add <code>-w myproject -p</code> to persist files to the cloud between runs.</p>
116
116
  </article>
117
117
  </div>
118
118
  </div>
@@ -213,17 +213,18 @@ mags login</code></pre>
213
213
 
214
214
  <div class="ref-section">
215
215
  <h3>Run Flags</h3>
216
+ <p><code>-w</code> and <code>-n</code> are aliases &mdash; both set the job name and workspace ID.</p>
216
217
  <div class="ref-table-wrap">
217
218
  <table class="ref-table">
218
219
  <thead><tr><th>Flag</th><th>Description</th></tr></thead>
219
220
  <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>
221
+ <tr><td><code>-w, --workspace &lt;name&gt;</code></td><td>Name the job/workspace. Data stays local to the VM only. Add <code>-p</code> to sync to S3 and persist.</td></tr>
222
+ <tr><td><code>-n, --name &lt;name&gt;</code></td><td>Alias for <code>-w</code></td></tr>
223
+ <tr><td><code>-p, --persistent</code></td><td>Keep sandbox alive after script, sync workspace to S3. Files persist across runs indefinitely.</td></tr>
224
+ <tr><td><code>--base &lt;workspace&gt;</code></td><td>Clone a workspace as a read-only starting point (OverlayFS)</td></tr>
223
225
  <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>
226
+ <tr><td><code>-e, --ephemeral</code></td><td>No workspace, no sync &mdash; fastest execution</td></tr>
225
227
  <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
228
  <tr><td><code>--url</code></td><td>Enable public HTTPS URL (requires <code>-p</code>)</td></tr>
228
229
  <tr><td><code>--port &lt;port&gt;</code></td><td>Port to expose for URL (default: 8080)</td></tr>
229
230
  <tr><td><code>--disk &lt;GB&gt;</code></td><td>Custom disk size in GB (default: 2)</td></tr>
@@ -238,15 +239,19 @@ mags login</code></pre>
238
239
  <pre><code># Run a one-off command
239
240
  mags run 'echo Hello World && uname -a'
240
241
 
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'
242
+ # Local workspace &mdash; data stays on VM only (no S3 sync)
243
+ # Good for data analysis and throwaway work
244
+ mags run -w analysis -f data.csv 'python3 analyze.py'
245
+
246
+ # Persistent workspace &mdash; files synced to S3, survive across runs
247
+ mags run -w myproject -p 'pip install flask requests'
248
+ mags run -w myproject -p 'python3 app.py'
244
249
 
245
250
  # 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
251
+ mags run -w golden -p 'apk add nodejs npm && npm install -g typescript'
252
+ mags sync golden # persist to cloud
253
+ mags run --base golden 'npm test' # read-only, changes discarded
254
+ mags run --base golden -w fork-1 -p 'npm test' # fork: load golden, save fork-1 to S3
250
255
 
251
256
  # SSH into a workspace (auto-starts sandbox if needed)
252
257
  mags ssh myproject
@@ -265,7 +270,7 @@ mags run -w worker -p --no-sleep 'python3 worker.py'
265
270
  # Upload files and run
266
271
  mags run -f script.py -f data.csv 'python3 script.py'
267
272
 
268
- # Ephemeral (fastest &mdash; no cloud sync)
273
+ # Ephemeral (fastest &mdash; no workspace at all)
269
274
  mags run -e 'uname -a && df -h'
270
275
 
271
276
  # Cron job
@@ -290,7 +295,7 @@ export MAGS_API_TOKEN="your-token"</code></pre>
290
295
  <tbody>
291
296
  <tr><td><code>run(script, **opts)</code></td><td>Submit a job (returns immediately)</td></tr>
292
297
  <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>
298
+ <tr><td><code>new(name, persistent=True)</code></td><td>Create VM sandbox (add persistent for S3)</td></tr>
294
299
  <tr><td><code>exec(name, command)</code></td><td>Run command on existing sandbox</td></tr>
295
300
  <tr><td><code>stop(name_or_id)</code></td><td>Stop a running job</td></tr>
296
301
  <tr><td><code>find_job(name_or_id)</code></td><td>Find job by name or workspace</td></tr>
@@ -318,11 +323,11 @@ export MAGS_API_TOKEN="your-token"</code></pre>
318
323
  <table class="ref-table">
319
324
  <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
320
325
  <tbody>
321
- <tr><td><code>workspace_id</code></td><td>Keep files across runs (synced to the cloud)</td></tr>
326
+ <tr><td><code>workspace_id</code></td><td>Name the workspace. Local only unless <code>persistent=True</code>.</td></tr>
327
+ <tr><td><code>persistent</code></td><td>Keep sandbox alive, sync workspace to S3. Files persist indefinitely.</td></tr>
322
328
  <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
329
  <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>
330
+ <tr><td><code>ephemeral</code></td><td>No workspace, no sync (fastest)</td></tr>
326
331
  <tr><td><code>file_ids</code></td><td>List of uploaded file IDs to include</td></tr>
327
332
  <tr><td><code>startup_command</code></td><td>Command to run when sandbox wakes</td></tr>
328
333
  </tbody>
@@ -339,23 +344,25 @@ m = Mags() # reads MAGS_API_TOKEN from env
339
344
  result = m.run_and_wait("echo Hello World")
340
345
  print(result["status"]) # "completed"
341
346
 
342
- # Create a persistent workspace
347
+ # Local workspace (no S3 sync, good for analysis)
348
+ m.run_and_wait("python3 analyze.py", workspace_id="analysis")
349
+
350
+ # Persistent workspace (synced to S3)
351
+ m.run("pip install flask",
352
+ workspace_id="my-project", persistent=True)
353
+
354
+ # Create a sandbox (local disk)
343
355
  m.new("my-project")
344
356
 
357
+ # Create with S3 persistence
358
+ m.new("my-project", persistent=True)
359
+
345
360
  # Execute commands on existing sandbox
346
361
  result = m.exec("my-project", "ls -la /root")
347
362
  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
363
 
357
364
  # Public URL
358
- m.new("webapp")
365
+ m.new("webapp", persistent=True)
359
366
  info = m.url("webapp", port=3000)
360
367
  print(info["url"]) # https://xyz.apps.magpiecloud.com
361
368
 
@@ -414,11 +421,11 @@ export MAGS_API_TOKEN="your-token"</code></pre>
414
421
  <table class="ref-table">
415
422
  <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
416
423
  <tbody>
417
- <tr><td><code>workspaceId</code></td><td>Keep files across runs (synced to the cloud)</td></tr>
424
+ <tr><td><code>workspaceId</code></td><td>Name the workspace. Local only unless <code>persistent: true</code>.</td></tr>
425
+ <tr><td><code>persistent</code></td><td>Keep sandbox alive, sync workspace to S3. Files persist indefinitely.</td></tr>
418
426
  <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
427
  <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>
428
+ <tr><td><code>ephemeral</code></td><td>No workspace, no sync (fastest)</td></tr>
422
429
  <tr><td><code>fileIds</code></td><td>Array of uploaded file IDs to include</td></tr>
423
430
  <tr><td><code>startupCommand</code></td><td>Command to run when sandbox wakes</td></tr>
424
431
  </tbody>
@@ -435,13 +442,16 @@ const mags = new Mags({ apiToken: process.env.MAGS_API_TOKEN });
435
442
  const result = await mags.runAndWait('echo Hello World');
436
443
  console.log(result.status); // "completed"
437
444
 
438
- // Persistent workspace
439
- await mags.runAndWait('pip install flask', { workspaceId: 'myproject' });
440
- await mags.runAndWait('python3 app.py', { workspaceId: 'myproject' });
445
+ // Local workspace (no S3 sync, good for analysis)
446
+ await mags.runAndWait('python3 analyze.py', { workspaceId: 'analysis' });
447
+
448
+ // Persistent workspace (synced to S3)
449
+ await mags.runAndWait('pip install flask', { workspaceId: 'myproject', persistent: true });
450
+ await mags.runAndWait('python3 app.py', { workspaceId: 'myproject', persistent: true });
441
451
 
442
452
  // Base image
443
453
  await mags.runAndWait('npm test', { baseWorkspaceId: 'golden' });
444
- await mags.runAndWait('npm test', { baseWorkspaceId: 'golden', workspaceId: 'fork-1' });
454
+ await mags.runAndWait('npm test', { baseWorkspaceId: 'golden', workspaceId: 'fork-1', persistent: true });
445
455
 
446
456
  // SSH access
447
457
  const job = await mags.run('sleep 3600', { workspaceId: 'dev', persistent: true });
@@ -488,10 +498,10 @@ await mags.cronCreate({
488
498
  <article class="panel" data-reveal>
489
499
  <h3>Backed up to object storage</h3>
490
500
  <ul class="list">
491
- <li>Files, packages, and configs persist automatically</li>
492
- <li>Optional backup to S3-compatible object storage</li>
501
+ <li>Use <code>-w</code> for a local workspace (no cloud sync, good for throwaway analysis)</li>
502
+ <li>Add <code>-p</code> to sync to S3 &mdash; files, packages, and configs persist indefinitely</li>
493
503
  <li>Clone a workspace as a read-only base for new sandboxes</li>
494
- <li>Survives reboots, sleep, and agent restarts</li>
504
+ <li>Survives reboots, sleep, and agent restarts (with <code>-p</code>)</li>
495
505
  </ul>
496
506
  </article>
497
507
  <article class="panel" data-reveal>
@@ -569,8 +579,8 @@ await mags.run('node server.js', {
569
579
 
570
580
  m = Mags() # reads MAGS_API_TOKEN from env
571
581
 
572
- # Create a workspace, run commands on it
573
- m.new("demo")
582
+ # Create a sandbox, run commands on it
583
+ m.new("demo") # local disk; use persistent=True for S3
574
584
  result = m.exec("demo", "uname -a")
575
585
  print(result["output"])
576
586
 
@@ -583,7 +593,7 @@ print(result["status"]) # "completed"</code></pre>
583
593
  <ul class="list">
584
594
  <li><code>run(script, **opts)</code> &mdash; submit a job</li>
585
595
  <li><code>run_and_wait(script, **opts)</code> &mdash; submit + block</li>
586
- <li><code>new(name)</code> &mdash; create persistent VM</li>
596
+ <li><code>new(name, persistent=True)</code> &mdash; create VM sandbox (add persistent for S3)</li>
587
597
  <li><code>exec(name, command)</code> &mdash; run on existing sandbox</li>
588
598
  <li><code>stop(name_or_id)</code> &mdash; stop a job</li>
589
599
  <li><code>find_job(name_or_id)</code> &mdash; find by name/workspace</li>
package/website/llms.txt CHANGED
@@ -1,8 +1,8 @@
1
1
  # Mags
2
2
 
3
- > Instant sandboxes and runtime environments
3
+ > Secure cloud sandboxes for AI agents and developers
4
4
 
5
- Mags is a CLI, SDK, and API for running scripts on isolated microVMs with persistent workspaces, file upload, cron scheduling, and optional URL access.
5
+ Mags is a CLI, SDK, and API for running scripts on isolated microVMs with workspaces, file upload, cron scheduling, and optional URL access.
6
6
 
7
7
  ## Quickstart
8
8
 
@@ -26,32 +26,55 @@ mags run 'echo Hello World'
26
26
  - `mags login` — Authenticate and save credentials
27
27
  - `mags logout` — Remove saved credentials
28
28
  - `mags whoami` — Check auth status
29
- - `mags new <workspace>` — Create a persistent VM workspace
29
+ - `mags new <name> [-p]` — Create a VM sandbox (add -p for S3 data persistence)
30
30
  - `mags run <script>` — Execute a script on a microVM
31
- - `mags ssh <workspace>` — Open an SSH session into a workspace
32
- - `mags exec <workspace> <command>` — Run a command on an existing sandbox
33
- - `mags url <job-id>` — Enable public URL access for a running job
31
+ - `mags ssh <name>` — Open an SSH session into a sandbox (auto-starts if needed)
32
+ - `mags exec <name> <command>` — Run a command on an existing sandbox
34
33
  - `mags list` — List recent jobs
35
- - `mags logs <job-id>` — View job logs
36
- - `mags status <job-id>` — Check job status
37
- - `mags stop <job-id>` — Stop a running job
34
+ - `mags logs <name|id>` — View job logs
35
+ - `mags status <name|id>` — Check job status
36
+ - `mags stop <name|id>` — Stop a running job
37
+ - `mags sync <name|id>` — Sync workspace to S3 without stopping the VM
38
+ - `mags resize <name> --disk <GB>` — Resize disk for a workspace
39
+ - `mags url <name|id> [port]` — Enable public URL access for a running job
40
+ - `mags url alias <subdomain> <name>` — Create a stable URL alias
41
+ - `mags url alias list` — List URL aliases
42
+ - `mags url alias remove <subdomain>` — Remove a URL alias
38
43
  - `mags set <name|id> --no-sleep` — Never auto-sleep this VM
39
44
  - `mags set <name|id> --sleep` — Re-enable auto-sleep
45
+ - `mags workspace list` — List persistent workspaces
46
+ - `mags workspace delete <id>` — Delete workspace and cloud data
47
+ - `mags browser [name]` — Start a Chromium browser session (CDP access)
40
48
 
41
49
  ## Run Options
42
50
 
51
+ `-w` and `-n` are aliases — both set the job name and workspace ID to the same value.
52
+
43
53
  | Flag | Description |
44
54
  |------|-------------|
45
- | `-w, --workspace <id>` | Persist files with a named workspace |
46
- | `-n, --name <name>` | Job name for easier reference |
47
- | `-p, --persistent` | Keep VM alive after script for URL or SSH |
55
+ | `-w, --workspace <name>` | Name the job and workspace. Data stays local to the VM only (not synced to S3). Useful for data analysis where you don't need artifacts to persist. Combine with `-p` to sync to S3 and persist. |
56
+ | `-n, --name <name>` | Alias for `-w` |
57
+ | `-p, --persistent` | Keep VM alive after script and sync workspace to S3. Files persist across runs indefinitely. Required for URL, SSH, and cloud persistence. |
58
+ | `--base <workspace>` | Mount an existing workspace read-only as a starting point (OverlayFS). Use with `-w` to fork into a new workspace. |
48
59
  | `--no-sleep` | Never auto-sleep this VM (requires -p) |
49
- | `-e, --ephemeral` | No workspace, no S3 sync (fastest). Cannot combine with -w or -p |
60
+ | `-e, --ephemeral` | No workspace, no sync (fastest). Cannot combine with -w, -p, or --base |
50
61
  | `-f, --file <path>` | Upload a local file into /root/ in the VM (repeatable) |
51
62
  | `--url` | Enable public URL (requires -p) |
52
63
  | `--port <port>` | Port to expose (default: 8080) |
53
64
  | `--disk <GB>` | Custom disk size in GB (default: 2) |
54
- | `--startup-command <cmd>` | Command to run when a VM wakes |
65
+ | `--startup-command <cmd>` | Command to run when a VM wakes from sleep |
66
+
67
+ ## Workspace Modes
68
+
69
+ | Flags | Behavior |
70
+ |-------|----------|
71
+ | (none) | Ephemeral. No workspace, no persistence. |
72
+ | `-w myproject` | Local workspace. Data stays on the VM only (~5 min warm cache after job completes). Not synced to S3. Good for data analysis and throwaway work. |
73
+ | `-w myproject -p` | Persistent workspace. VM stays alive, data synced to S3. Files survive across runs indefinitely. |
74
+ | `--base golden` | Read-only base image. Changes discarded after run. |
75
+ | `--base golden -w fork-1` | Fork: starts from golden, saves changes to fork-1 (local only without -p). |
76
+ | `--base golden -w fork-1 -p` | Fork with persistence: starts from golden, saves to fork-1 in S3. |
77
+ | `-e` | Explicit ephemeral. Same as no flags but cannot accidentally combine with -w or -p. |
55
78
 
56
79
  ## File Upload
57
80
 
@@ -64,8 +87,8 @@ mags run -f script.py 'python3 script.py'
64
87
  # Multiple files
65
88
  mags run -f analyze.py -f data.csv 'python3 analyze.py'
66
89
 
67
- # With workspace persistence
68
- mags run -w my-project -f config.json -f app.js 'cp *.js *.json /root/ && cd /root && npm install'
90
+ # With persistent workspace
91
+ mags run -w my-project -p -f config.json -f app.js 'cp *.js *.json /root/ && cd /root && npm install'
69
92
  ```
70
93
 
71
94
  ## Cron Scheduling
@@ -96,24 +119,20 @@ mags cron disable <id>
96
119
  |------|-------------|
97
120
  | `-n, --name` | Cron job name (required) |
98
121
  | `-s, --schedule` | Cron expression, e.g. "0 */2 * * *" (required) |
99
- | `-w, --workspace` | Workspace ID for persistent storage |
122
+ | `-w, --workspace` | Workspace ID for storage |
100
123
  | `-p, --persistent` | Keep VM alive after script |
101
124
 
102
- ## Workspaces
103
-
104
- Workspaces persist files, installed packages, and configuration between runs. Running processes, in-memory state, and open ports reset between sessions.
105
-
106
125
  ## Complex Project Workflow
107
126
 
108
- For multi-file projects, the recommended pattern is: create files locally, upload them, then run.
127
+ For multi-file projects, the recommended pattern is: create files locally, upload them, then run persistently.
109
128
 
110
129
  ```
111
- # Step 1: Upload files and install deps in a workspace
112
- mags run -w my-project -f server.js -f package.json \
130
+ # Step 1: Upload files and install deps in a persistent workspace
131
+ mags run -w my-project -p -f server.js -f package.json \
113
132
  'cp *.js *.json /root/ && cd /root && npm install'
114
133
 
115
- # Step 2: Test run
116
- mags run -w my-project 'cd /root && node server.js'
134
+ # Step 2: Test run (reuses the persistent workspace)
135
+ mags run -w my-project -p 'cd /root && node server.js'
117
136
 
118
137
  # Step 3: Run with public URL
119
138
  mags run -w my-project -p --url --port 3000 'cd /root && node server.js'
@@ -123,9 +142,27 @@ mags cron add --name "my-server" --schedule "0 */6 * * *" -w my-project \
123
142
  'cd /root && node server.js'
124
143
  ```
125
144
 
145
+ ## Data Analysis Workflow
146
+
147
+ For throwaway analysis where you need a workspace but don't care about persisting artifacts:
148
+
149
+ ```
150
+ # Upload data and analyze — workspace is local only, no S3 overhead
151
+ mags run -w analysis -f data.csv 'python3 -c "
152
+ import csv
153
+ with open(\"/root/data.csv\") as f:
154
+ reader = csv.reader(f)
155
+ for row in reader:
156
+ print(row)
157
+ "'
158
+
159
+ # Results printed to stdout (use `mags logs` to retrieve)
160
+ # VM and data cleaned up automatically after ~5 minutes
161
+ ```
162
+
126
163
  ## Ephemeral Mode
127
164
 
128
- For the fastest runs with no workspace or S3 sync overhead:
165
+ For the fastest runs with no workspace at all:
129
166
  ```
130
167
  mags run -e 'uname -a && df -h'
131
168
  ```
@@ -150,12 +187,17 @@ Generate tokens at https://mags.run/tokens or via `mags login`.
150
187
  - `GET /mags-jobs/{id}/status` — Get job status
151
188
  - `GET /mags-jobs/{id}/logs` — Get job logs
152
189
  - `POST /mags-jobs/{id}/access` — Enable URL or SSH access
153
- - `PATCH /mags-jobs/{id}` — Update startup command
190
+ - `PATCH /mags-jobs/{id}` — Update job settings (startup_command, no_sleep)
154
191
 
155
192
  ### File Endpoints
156
193
 
157
194
  - `POST /mags-files` — Upload file(s) via multipart/form-data (max 100MB)
158
195
 
196
+ ### Workspace Endpoints
197
+
198
+ - `GET /mags-workspaces` — List workspaces
199
+ - `DELETE /mags-workspaces/{id}` — Delete workspace and S3 data
200
+
159
201
  ### Cron Endpoints
160
202
 
161
203
  - `POST /mags-cron` — Create a cron job
@@ -193,9 +235,12 @@ mags = Mags() # uses MAGS_API_TOKEN env var
193
235
  result = mags.run_and_wait('echo Hello World')
194
236
  print(result['logs'])
195
237
 
196
- # Create a persistent workspace
238
+ # Create a sandbox (local disk)
197
239
  mags.new('my-project')
198
240
 
241
+ # Create with S3 persistence
242
+ mags.new('my-project', persistent=True)
243
+
199
244
  # Execute a command on an existing sandbox
200
245
  result = mags.exec('my-project', 'ls -la /root')
201
246
  print(result['output'])
@@ -265,8 +310,8 @@ Then use `/mags <description>` in Claude Code to run scripts in microVMs.
265
310
 
266
311
  - Alpine Linux base image
267
312
  - Hostname: `mags-vm`
268
- - Persistent home: `/root` (synced to S3 with workspaces)
269
- - JuiceFS mount: `/jfs`
313
+ - Home directory: `/root`
314
+ - JuiceFS mount: `/jfs` (persistent workspaces only)
270
315
  - Package manager: `apk`
271
316
  - Pre-installed: curl, wget, git, python3, node, vim, nano
272
317
 
package/website/mags.md CHANGED
@@ -31,9 +31,10 @@ mags run -p --url '<script>'
31
31
  mags run -w <workspace> -p --url --port 3000 '<script>'
32
32
  ```
33
33
 
34
- ### Create a new persistent VM (stays alive for SSH)
34
+ ### Create a new VM sandbox (stays alive for SSH)
35
35
  ```bash
36
- mags new <name>
36
+ mags new <name> # Local disk only
37
+ mags new <name> -p # With S3 data persistence
37
38
  ```
38
39
 
39
40
  ### Execute a command on an existing sandbox