@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 +14 -10
- package/bin/mags.js +14 -5
- package/index.js +7 -2
- package/nodejs/README.md +10 -4
- package/package.json +1 -1
- package/python/README.md +1 -1
- package/python/pyproject.toml +1 -1
- package/python/src/mags/client.py +13 -3
- package/website/api.html +3 -3
- package/website/cookbook.html +1 -1
- package/website/index.html +53 -43
- package/website/llms.txt +76 -31
- package/website/mags.md +3 -2
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
|
-
-
|
|
11
|
-
- Syncs /root directory automatically
|
|
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
|
|
54
|
+
### Create a sandbox
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
# Create a new VM
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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] === '
|
|
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>]
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
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
|
|
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 |
|
package/python/pyproject.toml
CHANGED
|
@@ -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,
|
|
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
|
|
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>
|
|
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>
|
|
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>
|
package/website/cookbook.html
CHANGED
|
@@ -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
|
|
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>
|
package/website/index.html
CHANGED
|
@@ -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,
|
|
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
|
|
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 — 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 <
|
|
221
|
-
<tr><td><code
|
|
222
|
-
<tr><td><code>-p, --persistent</code></td><td>Keep sandbox alive after script
|
|
221
|
+
<tr><td><code>-w, --workspace <name></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 <name></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 <workspace></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
|
|
226
|
+
<tr><td><code>-e, --ephemeral</code></td><td>No workspace, no sync — fastest execution</td></tr>
|
|
225
227
|
<tr><td><code>-f, --file <path></code></td><td>Upload file(s) into the sandbox (repeatable)</td></tr>
|
|
226
|
-
<tr><td><code>-n, --name <name></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 <port></code></td><td>Port to expose for URL (default: 8080)</td></tr>
|
|
229
230
|
<tr><td><code>--disk <GB></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
|
-
#
|
|
242
|
-
|
|
243
|
-
mags run -w
|
|
242
|
+
# Local workspace — 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 — 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 — create a golden image, sync it, then reuse
|
|
246
|
-
mags run -w golden -p '
|
|
247
|
-
mags sync golden
|
|
248
|
-
mags run --base golden 'npm test'
|
|
249
|
-
mags run --base golden -w fork-1 'npm test'
|
|
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 — no
|
|
273
|
+
# Ephemeral (fastest — 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
|
|
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>
|
|
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
|
|
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
|
-
#
|
|
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>
|
|
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
|
|
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
|
-
//
|
|
439
|
-
await mags.runAndWait('
|
|
440
|
-
|
|
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>
|
|
492
|
-
<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 — 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
|
|
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> — submit a job</li>
|
|
585
595
|
<li><code>run_and_wait(script, **opts)</code> — submit + block</li>
|
|
586
|
-
<li><code>new(name)</code> — create persistent
|
|
596
|
+
<li><code>new(name, persistent=True)</code> — create VM sandbox (add persistent for S3)</li>
|
|
587
597
|
<li><code>exec(name, command)</code> — run on existing sandbox</li>
|
|
588
598
|
<li><code>stop(name_or_id)</code> — stop a job</li>
|
|
589
599
|
<li><code>find_job(name_or_id)</code> — find by name/workspace</li>
|
package/website/llms.txt
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Mags
|
|
2
2
|
|
|
3
|
-
>
|
|
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
|
|
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 <
|
|
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 <
|
|
32
|
-
- `mags exec <
|
|
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 <
|
|
36
|
-
- `mags status <
|
|
37
|
-
- `mags stop <
|
|
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 <
|
|
46
|
-
| `-n, --name <name>` |
|
|
47
|
-
| `-p, --persistent` | Keep VM alive after script for URL
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|