@magpiecloud/mags 1.6.2 → 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 +40 -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
|
|
@@ -687,6 +688,40 @@ async function stopJob(nameOrId) {
|
|
|
687
688
|
}
|
|
688
689
|
}
|
|
689
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
|
+
|
|
690
725
|
// Download a file from URL
|
|
691
726
|
function downloadFile(url) {
|
|
692
727
|
return new Promise((resolve, reject) => {
|
|
@@ -1215,7 +1250,7 @@ async function main() {
|
|
|
1215
1250
|
break;
|
|
1216
1251
|
case '--version':
|
|
1217
1252
|
case '-v':
|
|
1218
|
-
console.log('mags v1.
|
|
1253
|
+
console.log('mags v1.7.0');
|
|
1219
1254
|
process.exit(0);
|
|
1220
1255
|
break;
|
|
1221
1256
|
case 'new':
|
|
@@ -1250,6 +1285,10 @@ async function main() {
|
|
|
1250
1285
|
await requireAuth();
|
|
1251
1286
|
await stopJob(args[1]);
|
|
1252
1287
|
break;
|
|
1288
|
+
case 'sync':
|
|
1289
|
+
await requireAuth();
|
|
1290
|
+
await syncWorkspace(args[1]);
|
|
1291
|
+
break;
|
|
1253
1292
|
case 'cron':
|
|
1254
1293
|
await requireAuth();
|
|
1255
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 {
|