@magpiecloud/mags 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mags.js +53 -1
- package/package.json +1 -1
- package/website/index.html +238 -276
- package/website/styles.css +63 -0
package/bin/mags.js
CHANGED
|
@@ -207,6 +207,7 @@ ${colors.bold}Commands:${colors.reset}
|
|
|
207
207
|
list List recent jobs
|
|
208
208
|
url <name|id> [port] Enable URL access for a job
|
|
209
209
|
stop <name|id> Stop a running job
|
|
210
|
+
sync <workspace|id> Sync workspace to S3 (without stopping)
|
|
210
211
|
workspace list List persistent workspaces
|
|
211
212
|
workspace delete <id> Delete a workspace and its S3 data
|
|
212
213
|
setup-claude Install Mags skill for Claude Code
|
|
@@ -215,6 +216,7 @@ ${colors.bold}Run Options:${colors.reset}
|
|
|
215
216
|
-w, --workspace <id> Use persistent workspace (S3 sync)
|
|
216
217
|
-n, --name <name> Set job name (for easier reference)
|
|
217
218
|
-p, --persistent Keep VM alive after script completes
|
|
219
|
+
--base <workspace> Mount workspace read-only as base image
|
|
218
220
|
-e, --ephemeral No workspace/S3 sync (fastest execution)
|
|
219
221
|
-f, --file <path> Upload file(s) to VM (repeatable)
|
|
220
222
|
--url Enable public URL access (requires -p)
|
|
@@ -242,6 +244,8 @@ ${colors.bold}Examples:${colors.reset}
|
|
|
242
244
|
mags run -e 'echo fast' # Ephemeral (no S3 sync)
|
|
243
245
|
mags run -f script.py 'python3 script.py' # Upload + run file
|
|
244
246
|
mags run -w myproject 'python3 script.py'
|
|
247
|
+
mags run --base golden 'npm test' # Use golden as read-only base
|
|
248
|
+
mags run --base golden -w fork-1 'npm test' # Base + save changes to fork-1
|
|
245
249
|
mags run -p --url 'python3 -m http.server 8080'
|
|
246
250
|
mags run -n webapp -w webapp -p --url --port 3000 'npm start'
|
|
247
251
|
mags workspace list # List workspaces
|
|
@@ -436,6 +440,7 @@ async function newVM(name) {
|
|
|
436
440
|
async function runJob(args) {
|
|
437
441
|
let script = '';
|
|
438
442
|
let workspace = '';
|
|
443
|
+
let baseWorkspace = '';
|
|
439
444
|
let name = '';
|
|
440
445
|
let persistent = false;
|
|
441
446
|
let ephemeral = false;
|
|
@@ -455,6 +460,9 @@ async function runJob(args) {
|
|
|
455
460
|
case '--name':
|
|
456
461
|
name = args[++i];
|
|
457
462
|
break;
|
|
463
|
+
case '--base':
|
|
464
|
+
baseWorkspace = args[++i];
|
|
465
|
+
break;
|
|
458
466
|
case '-p':
|
|
459
467
|
case '--persistent':
|
|
460
468
|
persistent = true;
|
|
@@ -496,6 +504,10 @@ async function runJob(args) {
|
|
|
496
504
|
log('red', 'Error: Cannot use --ephemeral with --persistent; ephemeral VMs are destroyed after execution');
|
|
497
505
|
process.exit(1);
|
|
498
506
|
}
|
|
507
|
+
if (ephemeral && baseWorkspace) {
|
|
508
|
+
log('red', 'Error: Cannot use --ephemeral with --base; ephemeral VMs have no workspace support');
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
499
511
|
|
|
500
512
|
// Upload files if any
|
|
501
513
|
let fileIds = [];
|
|
@@ -523,6 +535,7 @@ async function runJob(args) {
|
|
|
523
535
|
// Only set workspace_id if not ephemeral
|
|
524
536
|
if (!ephemeral && workspace) payload.workspace_id = workspace;
|
|
525
537
|
if (name) payload.name = name;
|
|
538
|
+
if (baseWorkspace) payload.base_workspace_id = baseWorkspace;
|
|
526
539
|
if (startupCommand) payload.startup_command = startupCommand;
|
|
527
540
|
if (fileIds.length > 0) payload.file_ids = fileIds;
|
|
528
541
|
|
|
@@ -538,6 +551,7 @@ async function runJob(args) {
|
|
|
538
551
|
log('green', `Job submitted: ${requestId}`);
|
|
539
552
|
if (name) log('blue', `Name: ${name}`);
|
|
540
553
|
if (workspace) log('blue', `Workspace: ${workspace}`);
|
|
554
|
+
if (baseWorkspace) log('blue', `Base workspace: ${baseWorkspace} (read-only)`);
|
|
541
555
|
if (persistent) log('yellow', 'Persistent: VM will stay alive');
|
|
542
556
|
|
|
543
557
|
// Poll for completion
|
|
@@ -674,6 +688,40 @@ async function stopJob(nameOrId) {
|
|
|
674
688
|
}
|
|
675
689
|
}
|
|
676
690
|
|
|
691
|
+
async function syncWorkspace(nameOrId) {
|
|
692
|
+
if (!nameOrId) {
|
|
693
|
+
log('red', 'Error: Workspace name or job ID required');
|
|
694
|
+
console.log('\nUsage: mags sync <workspace|id>\n');
|
|
695
|
+
console.log('Syncs the workspace filesystem to S3 without stopping the VM.');
|
|
696
|
+
console.log('Use this after setting up a base image to persist your changes.\n');
|
|
697
|
+
console.log('Examples:');
|
|
698
|
+
console.log(' mags sync myproject');
|
|
699
|
+
console.log(' mags sync golden # after setting up base image');
|
|
700
|
+
process.exit(1);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Try to find a running job for this workspace
|
|
704
|
+
const existingJob = await findWorkspaceJob(nameOrId);
|
|
705
|
+
let requestId;
|
|
706
|
+
|
|
707
|
+
if (existingJob && existingJob.status === 'running') {
|
|
708
|
+
requestId = existingJob.request_id;
|
|
709
|
+
} else {
|
|
710
|
+
// Try as a direct job ID
|
|
711
|
+
requestId = await resolveJobId(nameOrId);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
log('blue', `Syncing workspace...`);
|
|
715
|
+
const resp = await request('POST', `/api/v1/mags-jobs/${requestId}/sync`);
|
|
716
|
+
if (resp.success) {
|
|
717
|
+
log('green', 'Workspace synced to S3 successfully');
|
|
718
|
+
} else {
|
|
719
|
+
log('red', 'Failed to sync workspace');
|
|
720
|
+
if (resp.error) log('red', resp.error);
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
677
725
|
// Download a file from URL
|
|
678
726
|
function downloadFile(url) {
|
|
679
727
|
return new Promise((resolve, reject) => {
|
|
@@ -1202,7 +1250,7 @@ async function main() {
|
|
|
1202
1250
|
break;
|
|
1203
1251
|
case '--version':
|
|
1204
1252
|
case '-v':
|
|
1205
|
-
console.log('mags v1.
|
|
1253
|
+
console.log('mags v1.7.0');
|
|
1206
1254
|
process.exit(0);
|
|
1207
1255
|
break;
|
|
1208
1256
|
case 'new':
|
|
@@ -1237,6 +1285,10 @@ async function main() {
|
|
|
1237
1285
|
await requireAuth();
|
|
1238
1286
|
await stopJob(args[1]);
|
|
1239
1287
|
break;
|
|
1288
|
+
case 'sync':
|
|
1289
|
+
await requireAuth();
|
|
1290
|
+
await syncWorkspace(args[1]);
|
|
1291
|
+
break;
|
|
1240
1292
|
case 'cron':
|
|
1241
1293
|
await requireAuth();
|
|
1242
1294
|
await cronCommand(args.slice(1));
|
package/package.json
CHANGED
package/website/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/>
|
|
11
11
|
<meta name="api-base" content="https://api.magpiecloud.com" />
|
|
12
12
|
<meta name="auth-base" content="https://api.magpiecloud.com" />
|
|
13
|
-
<link rel="stylesheet" href="styles.css?v=
|
|
13
|
+
<link rel="stylesheet" href="styles.css?v=3" />
|
|
14
14
|
<script src="env.js"></script>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -174,317 +174,279 @@ console.log(result.status); // "completed"</code></pre>
|
|
|
174
174
|
<div class="container">
|
|
175
175
|
<div class="section-title">
|
|
176
176
|
<p>Usage Patterns</p>
|
|
177
|
-
<h2>
|
|
177
|
+
<h2>Everything you can do with Mags.</h2>
|
|
178
178
|
</div>
|
|
179
|
-
<div class="
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
<h4>Run a script</h4>
|
|
185
|
-
<p class="pattern-desc">Execute a one-off command in a fresh microVM.</p>
|
|
186
|
-
</div>
|
|
187
|
-
<span class="chevron">▼</span>
|
|
188
|
-
</div>
|
|
189
|
-
<div class="pattern-body tab-group">
|
|
190
|
-
<div class="tab-bar">
|
|
191
|
-
<button class="tab active" data-tab="p1-cli">CLI</button>
|
|
192
|
-
<button class="tab" data-tab="p1-python">Python</button>
|
|
193
|
-
<button class="tab" data-tab="p1-node">Node.js</button>
|
|
194
|
-
</div>
|
|
195
|
-
<div class="tab-content active" data-tab="p1-cli">
|
|
196
|
-
<pre><code>mags run 'echo Hello World && uname -a'</code></pre>
|
|
197
|
-
</div>
|
|
198
|
-
<div class="tab-content" data-tab="p1-python">
|
|
199
|
-
<pre><code>result = m.run_and_wait("echo Hello World && uname -a")
|
|
200
|
-
print(result["status"]) # "completed"
|
|
201
|
-
print(result["exit_code"]) # 0</code></pre>
|
|
202
|
-
</div>
|
|
203
|
-
<div class="tab-content" data-tab="p1-node">
|
|
204
|
-
<pre><code>const result = await mags.runAndWait('echo Hello World && uname -a');
|
|
205
|
-
console.log(result.status); // "completed"
|
|
206
|
-
console.log(result.exitCode); // 0</code></pre>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
179
|
+
<div class="tab-group">
|
|
180
|
+
<div class="tab-bar">
|
|
181
|
+
<button class="tab active" data-tab="ref-cli">CLI</button>
|
|
182
|
+
<button class="tab" data-tab="ref-python">Python SDK</button>
|
|
183
|
+
<button class="tab" data-tab="ref-node">Node.js SDK</button>
|
|
209
184
|
</div>
|
|
210
185
|
|
|
211
|
-
<!--
|
|
212
|
-
<div class="
|
|
213
|
-
<div class="
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
</div>
|
|
218
|
-
<span class="chevron">▼</span>
|
|
186
|
+
<!-- ── CLI Reference ── -->
|
|
187
|
+
<div class="tab-content active" data-tab="ref-cli">
|
|
188
|
+
<div class="ref-section">
|
|
189
|
+
<h3>Install</h3>
|
|
190
|
+
<pre><code>npm install -g @magpiecloud/mags
|
|
191
|
+
mags login</code></pre>
|
|
219
192
|
</div>
|
|
220
|
-
<div class="pattern-body tab-group">
|
|
221
|
-
<div class="tab-bar">
|
|
222
|
-
<button class="tab active" data-tab="p2-cli">CLI</button>
|
|
223
|
-
<button class="tab" data-tab="p2-python">Python</button>
|
|
224
|
-
<button class="tab" data-tab="p2-node">Node.js</button>
|
|
225
|
-
</div>
|
|
226
|
-
<div class="tab-content active" data-tab="p2-cli">
|
|
227
|
-
<pre><code># First run: install dependencies
|
|
228
|
-
mags run -w myproject 'pip install flask requests'
|
|
229
193
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});</code></pre>
|
|
194
|
+
<div class="ref-section">
|
|
195
|
+
<h3>Commands</h3>
|
|
196
|
+
<div class="ref-table-wrap">
|
|
197
|
+
<table class="ref-table">
|
|
198
|
+
<thead><tr><th>Command</th><th>Description</th></tr></thead>
|
|
199
|
+
<tbody>
|
|
200
|
+
<tr><td><code>mags run <script></code></td><td>Execute a script in a fresh microVM</td></tr>
|
|
201
|
+
<tr><td><code>mags ssh <workspace></code></td><td>SSH into a VM (auto-starts if needed)</td></tr>
|
|
202
|
+
<tr><td><code>mags list</code></td><td>List recent jobs</td></tr>
|
|
203
|
+
<tr><td><code>mags status <id></code></td><td>Get job status</td></tr>
|
|
204
|
+
<tr><td><code>mags logs <id></code></td><td>Get job output</td></tr>
|
|
205
|
+
<tr><td><code>mags stop <id></code></td><td>Stop a running job</td></tr>
|
|
206
|
+
<tr><td><code>mags url <id> [port]</code></td><td>Enable public URL access</td></tr>
|
|
207
|
+
<tr><td><code>mags workspace list</code></td><td>List persistent workspaces</td></tr>
|
|
208
|
+
<tr><td><code>mags workspace delete <id></code></td><td>Delete workspace + S3 data</td></tr>
|
|
209
|
+
<tr><td><code>mags cron add [opts] <script></code></td><td>Create a scheduled cron job</td></tr>
|
|
210
|
+
<tr><td><code>mags cron list</code></td><td>List cron jobs</td></tr>
|
|
211
|
+
<tr><td><code>mags cron remove <id></code></td><td>Delete a cron job</td></tr>
|
|
212
|
+
</tbody>
|
|
213
|
+
</table>
|
|
251
214
|
</div>
|
|
252
215
|
</div>
|
|
253
|
-
</div>
|
|
254
216
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
217
|
+
<div class="ref-section">
|
|
218
|
+
<h3>Run Flags</h3>
|
|
219
|
+
<div class="ref-table-wrap">
|
|
220
|
+
<table class="ref-table">
|
|
221
|
+
<thead><tr><th>Flag</th><th>Description</th></tr></thead>
|
|
222
|
+
<tbody>
|
|
223
|
+
<tr><td><code>-w, --workspace <id></code></td><td>Persist files to S3 across runs</td></tr>
|
|
224
|
+
<tr><td><code>--base <workspace></code></td><td>Mount a workspace read-only as a base image</td></tr>
|
|
225
|
+
<tr><td><code>-p, --persistent</code></td><td>Keep VM alive after script (sleeps when idle)</td></tr>
|
|
226
|
+
<tr><td><code>-e, --ephemeral</code></td><td>No S3 sync — fastest execution</td></tr>
|
|
227
|
+
<tr><td><code>-f, --file <path></code></td><td>Upload file(s) to VM (repeatable)</td></tr>
|
|
228
|
+
<tr><td><code>-n, --name <name></code></td><td>Name the job for easy reference</td></tr>
|
|
229
|
+
<tr><td><code>--url</code></td><td>Enable public HTTPS URL (requires <code>-p</code>)</td></tr>
|
|
230
|
+
<tr><td><code>--port <port></code></td><td>Port to expose for URL (default: 8080)</td></tr>
|
|
231
|
+
<tr><td><code>--startup-command <cmd></code></td><td>Command to run when VM wakes from sleep</td></tr>
|
|
232
|
+
</tbody>
|
|
233
|
+
</table>
|
|
261
234
|
</div>
|
|
262
|
-
<span class="chevron">▼</span>
|
|
263
235
|
</div>
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
236
|
+
|
|
237
|
+
<div class="ref-section">
|
|
238
|
+
<h3>Examples</h3>
|
|
239
|
+
<pre><code># Run a one-off command
|
|
240
|
+
mags run 'echo Hello World && uname -a'
|
|
241
|
+
|
|
242
|
+
# Persistent workspace — files survive across runs
|
|
243
|
+
mags run -w myproject 'pip install flask requests'
|
|
244
|
+
mags run -w myproject 'python3 app.py'
|
|
245
|
+
|
|
246
|
+
# Base image — start from a pre-configured workspace
|
|
247
|
+
mags run --base golden 'npm test' # read-only, changes discarded
|
|
248
|
+
mags run --base golden -w fork-1 'npm test' # fork: load golden, save to fork-1
|
|
249
|
+
|
|
250
|
+
# SSH into a workspace (auto-starts VM if needed)
|
|
272
251
|
mags ssh myproject
|
|
273
252
|
|
|
274
|
-
#
|
|
275
|
-
mags
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
<pre><code># Start a persistent job, then enable SSH
|
|
279
|
-
job = m.run("sleep 3600", workspace_id="myproject", persistent=True)
|
|
253
|
+
# Persistent VM with public URL
|
|
254
|
+
mags run -w webapp -p --url --port 8080 \
|
|
255
|
+
--startup-command 'python3 -m http.server 8080' \
|
|
256
|
+
'python3 -m http.server 8080'
|
|
280
257
|
|
|
281
|
-
#
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
</div>
|
|
258
|
+
# Upload files and run
|
|
259
|
+
mags run -f script.py -f data.csv 'python3 script.py'
|
|
260
|
+
|
|
261
|
+
# Ephemeral (fastest — no S3 sync)
|
|
262
|
+
mags run -e 'uname -a && df -h'
|
|
263
|
+
|
|
264
|
+
# Cron job
|
|
265
|
+
mags cron add --name backup --schedule "0 0 * * *" \
|
|
266
|
+
-w backups 'tar czf backup.tar.gz /data'</code></pre>
|
|
291
267
|
</div>
|
|
292
268
|
</div>
|
|
293
269
|
|
|
294
|
-
<!--
|
|
295
|
-
<div class="
|
|
296
|
-
<div class="
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
</div>
|
|
301
|
-
<span class="chevron">▼</span>
|
|
270
|
+
<!-- ── Python SDK Reference ── -->
|
|
271
|
+
<div class="tab-content" data-tab="ref-python">
|
|
272
|
+
<div class="ref-section">
|
|
273
|
+
<h3>Install</h3>
|
|
274
|
+
<pre><code>pip install magpie-mags
|
|
275
|
+
export MAGS_API_TOKEN="your-token"</code></pre>
|
|
302
276
|
</div>
|
|
303
|
-
<div class="pattern-body tab-group">
|
|
304
|
-
<div class="tab-bar">
|
|
305
|
-
<button class="tab active" data-tab="p4-cli">CLI</button>
|
|
306
|
-
<button class="tab" data-tab="p4-python">Python</button>
|
|
307
|
-
<button class="tab" data-tab="p4-node">Node.js</button>
|
|
308
|
-
</div>
|
|
309
|
-
<div class="tab-content active" data-tab="p4-cli">
|
|
310
|
-
<pre><code>mags run -w webapp -p --url --port 8080 \
|
|
311
|
-
--startup-command "python3 -m http.server 8080" \
|
|
312
|
-
'echo "Hello" > index.html && python3 -m http.server 8080'
|
|
313
277
|
|
|
314
|
-
|
|
315
|
-
</
|
|
316
|
-
<div class="
|
|
317
|
-
<
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
278
|
+
<div class="ref-section">
|
|
279
|
+
<h3>Methods</h3>
|
|
280
|
+
<div class="ref-table-wrap">
|
|
281
|
+
<table class="ref-table">
|
|
282
|
+
<thead><tr><th>Method</th><th>Description</th></tr></thead>
|
|
283
|
+
<tbody>
|
|
284
|
+
<tr><td><code>run(script, **opts)</code></td><td>Submit a job (returns immediately)</td></tr>
|
|
285
|
+
<tr><td><code>run_and_wait(script, **opts)</code></td><td>Submit + block until complete</td></tr>
|
|
286
|
+
<tr><td><code>status(request_id)</code></td><td>Get job status</td></tr>
|
|
287
|
+
<tr><td><code>logs(request_id)</code></td><td>Get job logs</td></tr>
|
|
288
|
+
<tr><td><code>list_jobs()</code></td><td>List recent jobs</td></tr>
|
|
289
|
+
<tr><td><code>enable_access(id, port)</code></td><td>Enable URL or SSH access</td></tr>
|
|
290
|
+
<tr><td><code>upload_files(paths)</code></td><td>Upload files, returns file IDs</td></tr>
|
|
291
|
+
<tr><td><code>list_workspaces()</code></td><td>List persistent workspaces</td></tr>
|
|
292
|
+
<tr><td><code>delete_workspace(id)</code></td><td>Delete workspace + S3 data</td></tr>
|
|
293
|
+
<tr><td><code>cron_create(**opts)</code></td><td>Create a cron job</td></tr>
|
|
294
|
+
<tr><td><code>cron_list()</code></td><td>List cron jobs</td></tr>
|
|
295
|
+
<tr><td><code>cron_delete(id)</code></td><td>Delete a cron job</td></tr>
|
|
296
|
+
<tr><td><code>usage(window_days)</code></td><td>Get usage stats</td></tr>
|
|
297
|
+
</tbody>
|
|
298
|
+
</table>
|
|
335
299
|
</div>
|
|
336
300
|
</div>
|
|
337
|
-
</div>
|
|
338
301
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
302
|
+
<div class="ref-section">
|
|
303
|
+
<h3>Run Options</h3>
|
|
304
|
+
<div class="ref-table-wrap">
|
|
305
|
+
<table class="ref-table">
|
|
306
|
+
<thead><tr><th>Parameter</th><th>Description</th></tr></thead>
|
|
307
|
+
<tbody>
|
|
308
|
+
<tr><td><code>workspace_id</code></td><td>Persist files to S3 across runs</td></tr>
|
|
309
|
+
<tr><td><code>base_workspace_id</code></td><td>Mount a workspace read-only as base image</td></tr>
|
|
310
|
+
<tr><td><code>persistent</code></td><td>Keep VM alive after script completes</td></tr>
|
|
311
|
+
<tr><td><code>ephemeral</code></td><td>No S3 sync (fastest)</td></tr>
|
|
312
|
+
<tr><td><code>file_ids</code></td><td>List of uploaded file IDs to include</td></tr>
|
|
313
|
+
<tr><td><code>startup_command</code></td><td>Command to run when VM wakes</td></tr>
|
|
314
|
+
</tbody>
|
|
315
|
+
</table>
|
|
345
316
|
</div>
|
|
346
|
-
<span class="chevron">▼</span>
|
|
347
317
|
</div>
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
318
|
+
|
|
319
|
+
<div class="ref-section">
|
|
320
|
+
<h3>Examples</h3>
|
|
321
|
+
<pre><code>from mags import Mags
|
|
322
|
+
m = Mags() # reads MAGS_API_TOKEN from env
|
|
323
|
+
|
|
324
|
+
# Run a command and wait
|
|
325
|
+
result = m.run_and_wait("echo Hello World")
|
|
326
|
+
print(result["status"]) # "completed"
|
|
327
|
+
|
|
328
|
+
# Persistent workspace
|
|
329
|
+
m.run_and_wait("pip install flask", workspace_id="myproject")
|
|
330
|
+
m.run_and_wait("python3 app.py", workspace_id="myproject")
|
|
331
|
+
|
|
332
|
+
# Base image
|
|
333
|
+
m.run_and_wait("npm test", base_workspace_id="golden")
|
|
334
|
+
m.run_and_wait("npm test", base_workspace_id="golden", workspace_id="fork-1")
|
|
335
|
+
|
|
336
|
+
# SSH access
|
|
337
|
+
job = m.run("sleep 3600", workspace_id="dev", persistent=True)
|
|
338
|
+
ssh = m.enable_access(job["request_id"], port=22)
|
|
339
|
+
print(f"ssh root@{ssh['ssh_host']} -p {ssh['ssh_port']}")
|
|
340
|
+
|
|
341
|
+
# Public URL
|
|
342
|
+
job = m.run("python3 -m http.server 8080",
|
|
343
|
+
workspace_id="webapp", persistent=True,
|
|
344
|
+
startup_command="python3 -m http.server 8080")
|
|
345
|
+
access = m.enable_access(job["request_id"], port=8080)
|
|
346
|
+
|
|
347
|
+
# Upload files
|
|
348
|
+
file_ids = m.upload_files(["script.py", "data.csv"])
|
|
349
|
+
m.run_and_wait("python3 /uploads/script.py", file_ids=file_ids)
|
|
350
|
+
|
|
351
|
+
# Workspaces
|
|
352
|
+
workspaces = m.list_workspaces()
|
|
353
|
+
m.delete_workspace("myproject")
|
|
354
|
+
|
|
355
|
+
# Cron
|
|
356
|
+
m.cron_create(name="backup", cron_expression="0 0 * * *",
|
|
357
|
+
script="tar czf backup.tar.gz /data", workspace_id="backups")</code></pre>
|
|
376
358
|
</div>
|
|
359
|
+
<p><a class="text-link" href="https://pypi.org/project/magpie-mags/" rel="noreferrer">View on PyPI →</a></p>
|
|
377
360
|
</div>
|
|
378
361
|
|
|
379
|
-
<!--
|
|
380
|
-
<div class="
|
|
381
|
-
<div class="
|
|
382
|
-
<
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
</div>
|
|
386
|
-
<span class="chevron">▼</span>
|
|
362
|
+
<!-- ── Node.js SDK Reference ── -->
|
|
363
|
+
<div class="tab-content" data-tab="ref-node">
|
|
364
|
+
<div class="ref-section">
|
|
365
|
+
<h3>Install</h3>
|
|
366
|
+
<pre><code>npm install @magpiecloud/mags
|
|
367
|
+
export MAGS_API_TOKEN="your-token"</code></pre>
|
|
387
368
|
</div>
|
|
388
|
-
<div class="pattern-body tab-group">
|
|
389
|
-
<div class="tab-bar">
|
|
390
|
-
<button class="tab active" data-tab="p6-cli">CLI</button>
|
|
391
|
-
<button class="tab" data-tab="p6-python">Python</button>
|
|
392
|
-
<button class="tab" data-tab="p6-rest">REST</button>
|
|
393
|
-
</div>
|
|
394
|
-
<div class="tab-content active" data-tab="p6-cli">
|
|
395
|
-
<pre><code>mags cron add \
|
|
396
|
-
--name "health-check" \
|
|
397
|
-
--schedule "0 */2 * * *" \
|
|
398
|
-
-w monitors \
|
|
399
|
-
'curl -sf https://myapp.com/health'
|
|
400
|
-
|
|
401
|
-
mags cron list
|
|
402
|
-
mags cron remove <id></code></pre>
|
|
403
|
-
</div>
|
|
404
|
-
<div class="tab-content" data-tab="p6-python">
|
|
405
|
-
<pre><code>cron = m.cron_create(
|
|
406
|
-
name="health-check",
|
|
407
|
-
cron_expression="0 */2 * * *",
|
|
408
|
-
script='curl -sf https://myapp.com/health',
|
|
409
|
-
workspace_id="monitors",
|
|
410
|
-
)
|
|
411
|
-
print(cron["id"])
|
|
412
369
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
370
|
+
<div class="ref-section">
|
|
371
|
+
<h3>Methods</h3>
|
|
372
|
+
<div class="ref-table-wrap">
|
|
373
|
+
<table class="ref-table">
|
|
374
|
+
<thead><tr><th>Method</th><th>Description</th></tr></thead>
|
|
375
|
+
<tbody>
|
|
376
|
+
<tr><td><code>run(script, opts)</code></td><td>Submit a job (returns immediately)</td></tr>
|
|
377
|
+
<tr><td><code>runAndWait(script, opts)</code></td><td>Submit + block until complete</td></tr>
|
|
378
|
+
<tr><td><code>status(requestId)</code></td><td>Get job status</td></tr>
|
|
379
|
+
<tr><td><code>logs(requestId)</code></td><td>Get job logs</td></tr>
|
|
380
|
+
<tr><td><code>listJobs()</code></td><td>List recent jobs</td></tr>
|
|
381
|
+
<tr><td><code>enableAccess(id, { port })</code></td><td>Enable URL or SSH access</td></tr>
|
|
382
|
+
<tr><td><code>uploadFile(path)</code></td><td>Upload a file, returns file ID</td></tr>
|
|
383
|
+
<tr><td><code>cronCreate(opts)</code></td><td>Create a cron job</td></tr>
|
|
384
|
+
<tr><td><code>cronList()</code></td><td>List cron jobs</td></tr>
|
|
385
|
+
<tr><td><code>cronDelete(id)</code></td><td>Delete a cron job</td></tr>
|
|
386
|
+
<tr><td><code>usage(windowDays)</code></td><td>Get usage stats</td></tr>
|
|
387
|
+
</tbody>
|
|
388
|
+
</table>
|
|
426
389
|
</div>
|
|
427
390
|
</div>
|
|
428
|
-
</div>
|
|
429
391
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
392
|
+
<div class="ref-section">
|
|
393
|
+
<h3>Run Options</h3>
|
|
394
|
+
<div class="ref-table-wrap">
|
|
395
|
+
<table class="ref-table">
|
|
396
|
+
<thead><tr><th>Parameter</th><th>Description</th></tr></thead>
|
|
397
|
+
<tbody>
|
|
398
|
+
<tr><td><code>workspaceId</code></td><td>Persist files to S3 across runs</td></tr>
|
|
399
|
+
<tr><td><code>baseWorkspaceId</code></td><td>Mount a workspace read-only as base image</td></tr>
|
|
400
|
+
<tr><td><code>persistent</code></td><td>Keep VM alive after script completes</td></tr>
|
|
401
|
+
<tr><td><code>ephemeral</code></td><td>No S3 sync (fastest)</td></tr>
|
|
402
|
+
<tr><td><code>fileIds</code></td><td>Array of uploaded file IDs to include</td></tr>
|
|
403
|
+
<tr><td><code>startupCommand</code></td><td>Command to run when VM wakes</td></tr>
|
|
404
|
+
</tbody>
|
|
405
|
+
</table>
|
|
436
406
|
</div>
|
|
437
|
-
<span class="chevron">▼</span>
|
|
438
407
|
</div>
|
|
439
|
-
<div class="pattern-body tab-group">
|
|
440
|
-
<div class="tab-bar">
|
|
441
|
-
<button class="tab active" data-tab="p7-cli">CLI</button>
|
|
442
|
-
<button class="tab" data-tab="p7-python">Python</button>
|
|
443
|
-
<button class="tab" data-tab="p7-rest">REST</button>
|
|
444
|
-
</div>
|
|
445
|
-
<div class="tab-content active" data-tab="p7-cli">
|
|
446
|
-
<pre><code>mags workspace list
|
|
447
|
-
mags workspace delete myproject</code></pre>
|
|
448
|
-
</div>
|
|
449
|
-
<div class="tab-content" data-tab="p7-python">
|
|
450
|
-
<pre><code>workspaces = m.list_workspaces()
|
|
451
|
-
for ws in workspaces["workspaces"]:
|
|
452
|
-
print(ws["workspace_id"], ws["job_count"])
|
|
453
408
|
|
|
454
|
-
|
|
455
|
-
</
|
|
456
|
-
<
|
|
457
|
-
|
|
458
|
-
curl .../mags-workspaces -H "Authorization: Bearer $TOKEN"
|
|
409
|
+
<div class="ref-section">
|
|
410
|
+
<h3>Examples</h3>
|
|
411
|
+
<pre><code>const Mags = require('@magpiecloud/mags');
|
|
412
|
+
const mags = new Mags({ apiToken: process.env.MAGS_API_TOKEN });
|
|
459
413
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
</div>
|
|
464
|
-
</div>
|
|
465
|
-
</div>
|
|
414
|
+
// Run a command and wait
|
|
415
|
+
const result = await mags.runAndWait('echo Hello World');
|
|
416
|
+
console.log(result.status); // "completed"
|
|
466
417
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
418
|
+
// Persistent workspace
|
|
419
|
+
await mags.runAndWait('pip install flask', { workspaceId: 'myproject' });
|
|
420
|
+
await mags.runAndWait('python3 app.py', { workspaceId: 'myproject' });
|
|
421
|
+
|
|
422
|
+
// Base image
|
|
423
|
+
await mags.runAndWait('npm test', { baseWorkspaceId: 'golden' });
|
|
424
|
+
await mags.runAndWait('npm test', { baseWorkspaceId: 'golden', workspaceId: 'fork-1' });
|
|
425
|
+
|
|
426
|
+
// SSH access
|
|
427
|
+
const job = await mags.run('sleep 3600', { workspaceId: 'dev', persistent: true });
|
|
428
|
+
const ssh = await mags.enableAccess(job.requestId, { port: 22 });
|
|
429
|
+
console.log(`ssh root@${ssh.sshHost} -p ${ssh.sshPort}`);
|
|
430
|
+
|
|
431
|
+
// Public URL
|
|
432
|
+
const webJob = await mags.run('python3 -m http.server 8080', {
|
|
433
|
+
workspaceId: 'webapp', persistent: true,
|
|
434
|
+
startupCommand: 'python3 -m http.server 8080',
|
|
435
|
+
});
|
|
436
|
+
const access = await mags.enableAccess(webJob.requestId, { port: 8080 });
|
|
437
|
+
console.log(access.url);
|
|
438
|
+
|
|
439
|
+
// Upload files
|
|
440
|
+
const fileId = await mags.uploadFile('script.py');
|
|
441
|
+
await mags.runAndWait('python3 /uploads/script.py', { fileIds: [fileId] });
|
|
442
|
+
|
|
443
|
+
// Cron
|
|
444
|
+
await mags.cronCreate({
|
|
445
|
+
name: 'backup', cronExpression: '0 0 * * *',
|
|
446
|
+
script: 'tar czf backup.tar.gz /data', workspaceId: 'backups',
|
|
447
|
+
});</code></pre>
|
|
487
448
|
</div>
|
|
449
|
+
<p><a class="text-link" href="https://www.npmjs.com/package/@magpiecloud/mags" rel="noreferrer">View on npm →</a></p>
|
|
488
450
|
</div>
|
|
489
451
|
</div>
|
|
490
452
|
</div>
|
|
@@ -702,7 +664,7 @@ console.log(result.logs);</code></pre>
|
|
|
702
664
|
</div>
|
|
703
665
|
</footer>
|
|
704
666
|
|
|
705
|
-
<script src="script.js?v=
|
|
667
|
+
<script src="script.js?v=3"></script>
|
|
706
668
|
<script>
|
|
707
669
|
(function() {
|
|
708
670
|
var token = localStorage.getItem('microvm-access-token');
|
package/website/styles.css
CHANGED
|
@@ -823,6 +823,69 @@ code {
|
|
|
823
823
|
display: block;
|
|
824
824
|
}
|
|
825
825
|
|
|
826
|
+
/* ── Reference tables ─────────────────────────────────── */
|
|
827
|
+
|
|
828
|
+
.ref-section {
|
|
829
|
+
margin-bottom: 2rem;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.ref-section h3 {
|
|
833
|
+
margin: 0 0 0.75rem;
|
|
834
|
+
font-size: 1rem;
|
|
835
|
+
font-weight: 600;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.ref-table-wrap {
|
|
839
|
+
border: 1px solid var(--border);
|
|
840
|
+
border-radius: var(--radius-sm);
|
|
841
|
+
overflow: hidden;
|
|
842
|
+
background: var(--surface);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.ref-table {
|
|
846
|
+
width: 100%;
|
|
847
|
+
border-collapse: collapse;
|
|
848
|
+
font-size: 0.88rem;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
.ref-table thead {
|
|
852
|
+
background: var(--surface-muted);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.ref-table th {
|
|
856
|
+
text-align: left;
|
|
857
|
+
padding: 0.65rem 1rem;
|
|
858
|
+
font-weight: 600;
|
|
859
|
+
font-size: 0.75rem;
|
|
860
|
+
text-transform: uppercase;
|
|
861
|
+
letter-spacing: 0.06em;
|
|
862
|
+
color: var(--text-muted);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.ref-table td {
|
|
866
|
+
padding: 0.6rem 1rem;
|
|
867
|
+
border-top: 1px solid var(--border);
|
|
868
|
+
vertical-align: top;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.ref-table td:first-child {
|
|
872
|
+
white-space: nowrap;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.ref-table code {
|
|
876
|
+
font-family: var(--mono);
|
|
877
|
+
font-size: 0.82rem;
|
|
878
|
+
background: var(--surface-muted);
|
|
879
|
+
padding: 0.15rem 0.4rem;
|
|
880
|
+
border-radius: 4px;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
@media (max-width: 768px) {
|
|
884
|
+
.ref-table td:first-child {
|
|
885
|
+
white-space: normal;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
826
889
|
/* ── Inline tab bar (for endpoint cards in api.html) ───── */
|
|
827
890
|
|
|
828
891
|
.code-tabs {
|