@magpiecloud/mags 1.5.1 → 1.6.1

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.
Files changed (42) hide show
  1. package/API.md +381 -0
  2. package/Mags-API.postman_collection.json +374 -0
  3. package/QUICKSTART.md +283 -0
  4. package/README.md +287 -79
  5. package/bin/mags.js +151 -27
  6. package/deploy-page.sh +171 -0
  7. package/index.js +1 -163
  8. package/mags +0 -0
  9. package/mags.sh +270 -0
  10. package/nodejs/README.md +191 -0
  11. package/nodejs/bin/mags.js +1146 -0
  12. package/nodejs/index.js +326 -0
  13. package/nodejs/package.json +42 -0
  14. package/package.json +4 -15
  15. package/python/INTEGRATION.md +747 -0
  16. package/python/README.md +139 -0
  17. package/python/dist/magpie_mags-1.0.0-py3-none-any.whl +0 -0
  18. package/python/dist/magpie_mags-1.0.0.tar.gz +0 -0
  19. package/python/examples/demo.py +181 -0
  20. package/python/pyproject.toml +39 -0
  21. package/python/src/magpie_mags.egg-info/PKG-INFO +164 -0
  22. package/python/src/magpie_mags.egg-info/SOURCES.txt +9 -0
  23. package/python/src/magpie_mags.egg-info/dependency_links.txt +1 -0
  24. package/python/src/magpie_mags.egg-info/requires.txt +1 -0
  25. package/python/src/magpie_mags.egg-info/top_level.txt +1 -0
  26. package/python/src/mags/__init__.py +6 -0
  27. package/python/src/mags/client.py +283 -0
  28. package/skill.md +153 -0
  29. package/website/api.html +927 -0
  30. package/website/claude-skill.html +483 -0
  31. package/website/cookbook/hn-marketing.html +410 -0
  32. package/website/cookbook/hn-marketing.sh +50 -0
  33. package/website/cookbook.html +278 -0
  34. package/website/env.js +4 -0
  35. package/website/index.html +718 -0
  36. package/website/llms.txt +242 -0
  37. package/website/login.html +88 -0
  38. package/website/mags.md +171 -0
  39. package/website/script.js +425 -0
  40. package/website/styles.css +845 -0
  41. package/website/tokens.html +171 -0
  42. package/website/usage.html +187 -0
@@ -0,0 +1,718 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Mags - Instant Sandboxes and Runtime Environments</title>
7
+ <meta
8
+ name="description"
9
+ content="Mags is a CLI, Python SDK, and Node.js SDK for running scripts on isolated microVMs with persistent workspaces and optional URL access."
10
+ />
11
+ <meta name="api-base" content="https://api.magpiecloud.com" />
12
+ <meta name="auth-base" content="https://api.magpiecloud.com" />
13
+ <link rel="stylesheet" href="styles.css?v=2" />
14
+ <script src="env.js"></script>
15
+ </head>
16
+ <body>
17
+ <div class="backdrop"></div>
18
+
19
+ <header class="site-header">
20
+ <div class="container nav">
21
+ <div class="brand">
22
+ <span class="logo">Mags</span>
23
+ <span class="tag">Instant sandboxes and runtime environments</span>
24
+ </div>
25
+ <nav class="nav-links">
26
+ <a href="#overview">Overview</a>
27
+ <a href="#quickstart">Quickstart</a>
28
+ <a href="#patterns">Usage</a>
29
+ <a href="#sdk">SDKs</a>
30
+ <a href="api.html">API Docs</a>
31
+ <a href="login.html" id="nav-auth-link">Login</a>
32
+ </nav>
33
+ <div class="nav-cta">
34
+ <a class="button ghost" href="login.html" id="cta-auth-link">Login</a>
35
+ </div>
36
+ </div>
37
+ </header>
38
+
39
+ <main>
40
+ <!-- ── Hero ────────────────────────────────────────── -->
41
+ <section class="hero" id="overview">
42
+ <div class="container hero-grid">
43
+ <div class="hero-copy">
44
+ <span class="pill">New: Python SDK</span>
45
+ <h1>Run scripts in clean microVMs with persistent workspaces.</h1>
46
+ <p class="lead">
47
+ Mags gives developers a CLI, Python SDK, and Node.js SDK for running
48
+ code in isolated microVMs. Keep files and packages between runs, and
49
+ expose a URL or SSH session when you need it.
50
+ </p>
51
+ <div class="hero-actions">
52
+ <a class="button" href="#quickstart">Quickstart</a>
53
+ <a class="button ghost" href="api.html">API Reference</a>
54
+ </div>
55
+ <div class="hero-meta">
56
+ <span>CLI, Python, and Node.js &mdash; pick your tool.</span>
57
+ <span>VMs boot in ~300ms. Workspaces persist to S3.</span>
58
+ </div>
59
+ </div>
60
+ <div class="hero-card tab-group" aria-label="Mags example">
61
+ <div class="tab-bar">
62
+ <button class="tab active" data-tab="hero-cli">CLI</button>
63
+ <button class="tab" data-tab="hero-python">Python</button>
64
+ <button class="tab" data-tab="hero-node">Node.js</button>
65
+ </div>
66
+ <div class="tab-content active" data-tab="hero-cli">
67
+ <pre><code>mags run 'echo Hello World'
68
+
69
+ mags run -w myproject 'pip install flask'
70
+ mags ssh myproject</code></pre>
71
+ <p class="card-note">Run a script, persist a workspace, then jump in with SSH.</p>
72
+ </div>
73
+ <div class="tab-content" data-tab="hero-python">
74
+ <pre><code>from mags import Mags
75
+
76
+ m = Mags()
77
+ result = m.run_and_wait("echo Hello World")
78
+ print(result["status"]) # "completed"</code></pre>
79
+ <p class="card-note">pip install magpie-mags</p>
80
+ </div>
81
+ <div class="tab-content" data-tab="hero-node">
82
+ <pre><code>const Mags = require('@magpiecloud/mags');
83
+ const mags = new Mags();
84
+
85
+ const result = await mags.runAndWait('echo Hello World');
86
+ console.log(result.logs);</code></pre>
87
+ <p class="card-note">npm install @magpiecloud/mags</p>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </section>
92
+
93
+ <!-- ── Quickstart ──────────────────────────────────── -->
94
+ <section class="section" id="quickstart">
95
+ <div class="container">
96
+ <div class="section-title">
97
+ <p>Quickstart</p>
98
+ <h2>Get from zero to your first run.</h2>
99
+ </div>
100
+ <div class="tab-group">
101
+ <div class="tab-bar">
102
+ <button class="tab active" data-tab="qs-cli">CLI</button>
103
+ <button class="tab" data-tab="qs-python">Python</button>
104
+ <button class="tab" data-tab="qs-node">Node.js</button>
105
+ </div>
106
+ <div class="tab-content active" data-tab="qs-cli">
107
+ <div class="grid cards">
108
+ <article class="card" data-reveal>
109
+ <h3>1. Install</h3>
110
+ <pre><code>npm install -g @magpiecloud/mags</code></pre>
111
+ </article>
112
+ <article class="card" data-reveal>
113
+ <h3>2. Authenticate</h3>
114
+ <pre><code>mags login</code></pre>
115
+ <p>Or set a token directly:</p>
116
+ <pre><code>export MAGS_API_TOKEN="your-token"</code></pre>
117
+ </article>
118
+ <article class="card" data-reveal>
119
+ <h3>3. Run</h3>
120
+ <pre><code>mags run 'echo Hello World'</code></pre>
121
+ <p>Add <code>-w myproject</code> to persist files between runs.</p>
122
+ </article>
123
+ </div>
124
+ </div>
125
+ <div class="tab-content" data-tab="qs-python">
126
+ <div class="grid cards">
127
+ <article class="card" data-reveal>
128
+ <h3>1. Install</h3>
129
+ <pre><code>pip install magpie-mags</code></pre>
130
+ </article>
131
+ <article class="card" data-reveal>
132
+ <h3>2. Set your token</h3>
133
+ <pre><code>export MAGS_API_TOKEN="your-token"</code></pre>
134
+ <p>Or pass it directly: <code>Mags(api_token="...")</code></p>
135
+ </article>
136
+ <article class="card" data-reveal>
137
+ <h3>3. Run</h3>
138
+ <pre><code>from mags import Mags
139
+
140
+ m = Mags()
141
+ result = m.run_and_wait("echo Hello World")
142
+ for log in result["logs"]:
143
+ print(log["message"])</code></pre>
144
+ </article>
145
+ </div>
146
+ </div>
147
+ <div class="tab-content" data-tab="qs-node">
148
+ <div class="grid cards">
149
+ <article class="card" data-reveal>
150
+ <h3>1. Install</h3>
151
+ <pre><code>npm install @magpiecloud/mags</code></pre>
152
+ </article>
153
+ <article class="card" data-reveal>
154
+ <h3>2. Set your token</h3>
155
+ <pre><code>export MAGS_API_TOKEN="your-token"</code></pre>
156
+ <p>Or pass it directly: <code>new Mags({ apiToken: "..." })</code></p>
157
+ </article>
158
+ <article class="card" data-reveal>
159
+ <h3>3. Run</h3>
160
+ <pre><code>const Mags = require('@magpiecloud/mags');
161
+ const mags = new Mags();
162
+
163
+ const result = await mags.runAndWait('echo Hello');
164
+ console.log(result.status); // "completed"</code></pre>
165
+ </article>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </section>
171
+
172
+ <!-- ── Usage Patterns ──────────────────────────────── -->
173
+ <section class="section" id="patterns">
174
+ <div class="container">
175
+ <div class="section-title">
176
+ <p>Usage Patterns</p>
177
+ <h2>Click a pattern to see how it works.</h2>
178
+ </div>
179
+ <div class="pattern-list">
180
+ <!-- Run a script -->
181
+ <div class="pattern-card open">
182
+ <div class="pattern-header">
183
+ <div>
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">&#9660;</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>
209
+ </div>
210
+
211
+ <!-- Persistent workspace -->
212
+ <div class="pattern-card">
213
+ <div class="pattern-header">
214
+ <div>
215
+ <h4>Persistent workspace</h4>
216
+ <p class="pattern-desc">Install packages once, reuse across runs. Files persist to S3.</p>
217
+ </div>
218
+ <span class="chevron">&#9660;</span>
219
+ </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
+
230
+ # Second run: they're already there
231
+ mags run -w myproject 'python3 -c "import flask; print(flask.__version__)"'</code></pre>
232
+ </div>
233
+ <div class="tab-content" data-tab="p2-python">
234
+ <pre><code># First run
235
+ m.run_and_wait("pip install flask requests", workspace_id="myproject")
236
+
237
+ # Second run: flask is already installed
238
+ m.run_and_wait(
239
+ 'python3 -c "import flask; print(flask.__version__)"',
240
+ workspace_id="myproject",
241
+ )</code></pre>
242
+ </div>
243
+ <div class="tab-content" data-tab="p2-node">
244
+ <pre><code>// First run
245
+ await mags.runAndWait('pip install flask requests', { workspaceId: 'myproject' });
246
+
247
+ // Second run: flask is already installed
248
+ await mags.runAndWait('python3 -c "import flask; print(flask.__version__)"', {
249
+ workspaceId: 'myproject',
250
+ });</code></pre>
251
+ </div>
252
+ </div>
253
+ </div>
254
+
255
+ <!-- SSH into a VM -->
256
+ <div class="pattern-card">
257
+ <div class="pattern-header">
258
+ <div>
259
+ <h4>SSH into a VM</h4>
260
+ <p class="pattern-desc">Open an interactive shell or run a command via SSH.</p>
261
+ </div>
262
+ <span class="chevron">&#9660;</span>
263
+ </div>
264
+ <div class="pattern-body tab-group">
265
+ <div class="tab-bar">
266
+ <button class="tab active" data-tab="p3-cli">CLI</button>
267
+ <button class="tab" data-tab="p3-python">Python</button>
268
+ <button class="tab" data-tab="p3-rest">REST</button>
269
+ </div>
270
+ <div class="tab-content active" data-tab="p3-cli">
271
+ <pre><code># Interactive shell
272
+ mags ssh myproject
273
+
274
+ # Run a single command
275
+ mags ssh myproject "cat /etc/os-release"</code></pre>
276
+ </div>
277
+ <div class="tab-content" data-tab="p3-python">
278
+ <pre><code># Start a persistent job, then enable SSH
279
+ job = m.run("sleep 3600", workspace_id="myproject", persistent=True)
280
+
281
+ # Enable SSH access
282
+ ssh = m.enable_access(job["request_id"], port=22)
283
+ print(f"ssh root@{ssh['ssh_host']} -p {ssh['ssh_port']}")</code></pre>
284
+ </div>
285
+ <div class="tab-content" data-tab="p3-rest">
286
+ <pre><code>curl -X POST .../mags-jobs/$ID/access \
287
+ -H "Authorization: Bearer $TOKEN" \
288
+ -d '{"port": 22}'
289
+ # Returns ssh_host, ssh_port, ssh_private_key</code></pre>
290
+ </div>
291
+ </div>
292
+ </div>
293
+
294
+ <!-- Expose a URL -->
295
+ <div class="pattern-card">
296
+ <div class="pattern-header">
297
+ <div>
298
+ <h4>Expose a public URL</h4>
299
+ <p class="pattern-desc">Run a web server and get a public HTTPS URL. Auto-wakes from sleep.</p>
300
+ </div>
301
+ <span class="chevron">&#9660;</span>
302
+ </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
+
314
+ # Output: https://&lt;subdomain&gt;.apps.magpiecloud.com</code></pre>
315
+ </div>
316
+ <div class="tab-content" data-tab="p4-python">
317
+ <pre><code>job = m.run(
318
+ 'echo "Hello" > index.html && python3 -m http.server 8080',
319
+ workspace_id="webapp",
320
+ persistent=True,
321
+ startup_command="python3 -m http.server 8080",
322
+ )
323
+ # Wait for running, then enable access
324
+ access = m.enable_access(job["request_id"], port=8080)
325
+ print(access["url"])</code></pre>
326
+ </div>
327
+ <div class="tab-content" data-tab="p4-node">
328
+ <pre><code>const job = await mags.run('python3 -m http.server 8080', {
329
+ workspaceId: 'webapp',
330
+ persistent: true,
331
+ startupCommand: 'python3 -m http.server 8080',
332
+ });
333
+ const access = await mags.enableAccess(job.requestId, { port: 8080 });
334
+ console.log(access.url);</code></pre>
335
+ </div>
336
+ </div>
337
+ </div>
338
+
339
+ <!-- Upload files -->
340
+ <div class="pattern-card">
341
+ <div class="pattern-header">
342
+ <div>
343
+ <h4>Upload files</h4>
344
+ <p class="pattern-desc">Upload local files into the VM before your script runs.</p>
345
+ </div>
346
+ <span class="chevron">&#9660;</span>
347
+ </div>
348
+ <div class="pattern-body tab-group">
349
+ <div class="tab-bar">
350
+ <button class="tab active" data-tab="p5-cli">CLI</button>
351
+ <button class="tab" data-tab="p5-python">Python</button>
352
+ <button class="tab" data-tab="p5-rest">REST</button>
353
+ </div>
354
+ <div class="tab-content active" data-tab="p5-cli">
355
+ <pre><code>mags run -f script.py -f data.csv \
356
+ 'python3 script.py'</code></pre>
357
+ </div>
358
+ <div class="tab-content" data-tab="p5-python">
359
+ <pre><code>file_ids = m.upload_files(["script.py", "data.csv"])
360
+ result = m.run_and_wait(
361
+ "python3 /uploads/script.py",
362
+ file_ids=file_ids,
363
+ )</code></pre>
364
+ </div>
365
+ <div class="tab-content" data-tab="p5-rest">
366
+ <pre><code># 1. Upload file
367
+ curl -X POST .../mags-files \
368
+ -H "Authorization: Bearer $TOKEN" \
369
+ -F "file=@script.py"
370
+ # Returns: { "file_id": "abc123" }
371
+
372
+ # 2. Use in job
373
+ curl -X POST .../mags-jobs \
374
+ -d '{ "script": "python3 /uploads/script.py", "file_ids": ["abc123"], "type": "inline" }'</code></pre>
375
+ </div>
376
+ </div>
377
+ </div>
378
+
379
+ <!-- Cron jobs -->
380
+ <div class="pattern-card">
381
+ <div class="pattern-header">
382
+ <div>
383
+ <h4>Schedule cron jobs</h4>
384
+ <p class="pattern-desc">Run scripts on a recurring schedule.</p>
385
+ </div>
386
+ <span class="chevron">&#9660;</span>
387
+ </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 &lt;id&gt;</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
+
413
+ # List and manage
414
+ jobs = m.cron_list()
415
+ m.cron_delete(cron["id"])</code></pre>
416
+ </div>
417
+ <div class="tab-content" data-tab="p6-rest">
418
+ <pre><code>curl -X POST .../mags-cron \
419
+ -H "Authorization: Bearer $TOKEN" \
420
+ -d '{
421
+ "name": "health-check",
422
+ "cron_expression": "0 */2 * * *",
423
+ "script": "curl -sf https://myapp.com/health",
424
+ "workspace_id": "monitors"
425
+ }'</code></pre>
426
+ </div>
427
+ </div>
428
+ </div>
429
+
430
+ <!-- Manage workspaces -->
431
+ <div class="pattern-card">
432
+ <div class="pattern-header">
433
+ <div>
434
+ <h4>Manage workspaces</h4>
435
+ <p class="pattern-desc">List and delete persistent workspaces.</p>
436
+ </div>
437
+ <span class="chevron">&#9660;</span>
438
+ </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
+
454
+ m.delete_workspace("myproject")</code></pre>
455
+ </div>
456
+ <div class="tab-content" data-tab="p7-rest">
457
+ <pre><code># List workspaces
458
+ curl .../mags-workspaces -H "Authorization: Bearer $TOKEN"
459
+
460
+ # Delete a workspace
461
+ curl -X DELETE .../mags-workspaces/myproject \
462
+ -H "Authorization: Bearer $TOKEN"</code></pre>
463
+ </div>
464
+ </div>
465
+ </div>
466
+
467
+ <!-- Ephemeral run -->
468
+ <div class="pattern-card">
469
+ <div class="pattern-header">
470
+ <div>
471
+ <h4>Ephemeral run (fastest)</h4>
472
+ <p class="pattern-desc">Skip S3 sync for maximum speed. No workspace data is saved.</p>
473
+ </div>
474
+ <span class="chevron">&#9660;</span>
475
+ </div>
476
+ <div class="pattern-body tab-group">
477
+ <div class="tab-bar">
478
+ <button class="tab active" data-tab="p8-cli">CLI</button>
479
+ <button class="tab" data-tab="p8-python">Python</button>
480
+ </div>
481
+ <div class="tab-content active" data-tab="p8-cli">
482
+ <pre><code>mags run -e 'uname -a && df -h'</code></pre>
483
+ </div>
484
+ <div class="tab-content" data-tab="p8-python">
485
+ <pre><code>result = m.run_and_wait("uname -a && df -h", ephemeral=True)</code></pre>
486
+ </div>
487
+ </div>
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </section>
492
+
493
+ <!-- ── Workspaces ──────────────────────────────────── -->
494
+ <section class="section" id="workspaces">
495
+ <div class="container">
496
+ <div class="section-title">
497
+ <p>Workspaces</p>
498
+ <h2>Persist what matters, reset the rest.</h2>
499
+ </div>
500
+ <div class="grid split">
501
+ <article class="panel" data-reveal>
502
+ <h3>What stays</h3>
503
+ <ul class="list">
504
+ <li>Files and directories in your workspace</li>
505
+ <li>Installed packages and dependencies</li>
506
+ <li>Configuration and dotfiles</li>
507
+ </ul>
508
+ </article>
509
+ <article class="panel" data-reveal>
510
+ <h3>What resets</h3>
511
+ <ul class="list">
512
+ <li>Running processes</li>
513
+ <li>In-memory state</li>
514
+ <li>Open ports</li>
515
+ </ul>
516
+ </article>
517
+ </div>
518
+ </div>
519
+ </section>
520
+
521
+ <!-- ── SDKs + API ──────────────────────────────────── -->
522
+ <section class="section" id="sdk">
523
+ <div class="container">
524
+ <div class="section-title">
525
+ <p>SDKs + API</p>
526
+ <h2>Automate runs with Python, Node.js, or REST.</h2>
527
+ </div>
528
+ <div class="tab-group">
529
+ <div class="tab-bar">
530
+ <button class="tab active" data-tab="sdk-python">Python</button>
531
+ <button class="tab" data-tab="sdk-node">Node.js</button>
532
+ <button class="tab" data-tab="sdk-rest">REST API</button>
533
+ </div>
534
+ <div class="tab-content active" data-tab="sdk-python">
535
+ <div class="grid split">
536
+ <article class="panel" data-reveal>
537
+ <h3>Install</h3>
538
+ <pre><code>pip install magpie-mags</code></pre>
539
+ <h3 style="margin-top:1.2rem">Quick example</h3>
540
+ <pre><code>from mags import Mags
541
+
542
+ m = Mags() # reads MAGS_API_TOKEN from env
543
+
544
+ # Run and wait
545
+ result = m.run_and_wait(
546
+ "echo Hello from a VM!",
547
+ workspace_id="demo",
548
+ )
549
+ print(result["status"])
550
+ for log in result["logs"]:
551
+ print(log["message"])</code></pre>
552
+ </article>
553
+ <article class="panel" data-reveal>
554
+ <h3>Available methods</h3>
555
+ <ul class="list">
556
+ <li><code>run(script, **opts)</code> &mdash; submit a job</li>
557
+ <li><code>run_and_wait(script, **opts)</code> &mdash; submit + block</li>
558
+ <li><code>status(request_id)</code> &mdash; get job status</li>
559
+ <li><code>logs(request_id)</code> &mdash; get job logs</li>
560
+ <li><code>list_jobs()</code> &mdash; list recent jobs</li>
561
+ <li><code>enable_access(id, port)</code> &mdash; URL or SSH</li>
562
+ <li><code>upload_files(paths)</code> &mdash; upload files</li>
563
+ <li><code>list_workspaces()</code> &mdash; list workspaces</li>
564
+ <li><code>delete_workspace(id)</code> &mdash; delete workspace</li>
565
+ <li><code>cron_create(**opts)</code> &mdash; create cron</li>
566
+ <li><code>cron_list()</code> / <code>cron_delete(id)</code></li>
567
+ <li><code>usage(window_days)</code> &mdash; usage stats</li>
568
+ </ul>
569
+ <p style="margin-top:1rem"><a class="text-link" href="https://pypi.org/project/magpie-mags/" rel="noreferrer">PyPI &rarr;</a></p>
570
+ </article>
571
+ </div>
572
+ </div>
573
+ <div class="tab-content" data-tab="sdk-node">
574
+ <div class="grid split">
575
+ <article class="panel" data-reveal>
576
+ <h3>Install</h3>
577
+ <pre><code>npm install @magpiecloud/mags</code></pre>
578
+ <h3 style="margin-top:1.2rem">Quick example</h3>
579
+ <pre><code>const Mags = require('@magpiecloud/mags');
580
+
581
+ const mags = new Mags({
582
+ apiToken: process.env.MAGS_API_TOKEN,
583
+ });
584
+
585
+ const result = await mags.runAndWait('echo Hello World');
586
+ console.log(result.status);
587
+ console.log(result.logs);</code></pre>
588
+ </article>
589
+ <article class="panel" data-reveal>
590
+ <h3>Available methods</h3>
591
+ <ul class="list">
592
+ <li><code>run(script, opts)</code> &mdash; submit a job</li>
593
+ <li><code>runAndWait(script, opts)</code> &mdash; submit + block</li>
594
+ <li><code>status(requestId)</code> &mdash; get job status</li>
595
+ <li><code>logs(requestId)</code> &mdash; get job logs</li>
596
+ <li><code>listJobs()</code> &mdash; list recent jobs</li>
597
+ <li><code>enableAccess(id, { port })</code> &mdash; URL or SSH</li>
598
+ <li><code>uploadFile(path)</code> &mdash; upload a file</li>
599
+ <li><code>cronCreate(opts)</code> &mdash; create cron</li>
600
+ <li><code>cronList()</code> / <code>cronDelete(id)</code></li>
601
+ <li><code>usage(windowDays)</code> &mdash; usage stats</li>
602
+ </ul>
603
+ <p style="margin-top:1rem"><a class="text-link" href="https://www.npmjs.com/package/@magpiecloud/mags" rel="noreferrer">npm &rarr;</a></p>
604
+ </article>
605
+ </div>
606
+ </div>
607
+ <div class="tab-content" data-tab="sdk-rest">
608
+ <div class="grid split">
609
+ <article class="panel" data-reveal>
610
+ <h3>Submit a job</h3>
611
+ <pre><code>curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
612
+ -H "Authorization: Bearer $MAGS_API_TOKEN" \
613
+ -H "Content-Type: application/json" \
614
+ -d '{
615
+ "script": "echo Hello World",
616
+ "type": "inline",
617
+ "workspace_id": "myproject"
618
+ }'</code></pre>
619
+ </article>
620
+ <article class="panel" data-reveal>
621
+ <h3>Endpoints</h3>
622
+ <ul class="list">
623
+ <li><code>POST /mags-jobs</code> &mdash; submit job</li>
624
+ <li><code>GET /mags-jobs</code> &mdash; list jobs</li>
625
+ <li><code>GET /mags-jobs/:id/status</code> &mdash; status</li>
626
+ <li><code>GET /mags-jobs/:id/logs</code> &mdash; logs</li>
627
+ <li><code>POST /mags-jobs/:id/access</code> &mdash; URL/SSH</li>
628
+ <li><code>PATCH /mags-jobs/:id</code> &mdash; update</li>
629
+ <li><code>POST /mags-files</code> &mdash; upload file</li>
630
+ <li><code>GET /mags-workspaces</code> &mdash; list ws</li>
631
+ <li><code>DELETE /mags-workspaces/:id</code> &mdash; delete ws</li>
632
+ <li><code>POST /mags-cron</code> &mdash; create cron</li>
633
+ <li><code>GET /mags-cron</code> &mdash; list cron</li>
634
+ <li><code>DELETE /mags-cron/:id</code> &mdash; delete cron</li>
635
+ </ul>
636
+ <p style="margin-top:1rem"><a class="text-link" href="api.html">Full API reference &rarr;</a></p>
637
+ </article>
638
+ </div>
639
+ </div>
640
+ </div>
641
+ </div>
642
+ </section>
643
+
644
+ <!-- ── Resources ───────────────────────────────────── -->
645
+ <section class="section" id="resources">
646
+ <div class="container">
647
+ <div class="section-title">
648
+ <p>Resources</p>
649
+ <h2>Extra pages for day-to-day work.</h2>
650
+ </div>
651
+ <div class="grid cards">
652
+ <article class="card" data-reveal>
653
+ <h3>Login</h3>
654
+ <p>Sign in with Google or email to access jobs and tokens.</p>
655
+ <a class="text-link" href="login.html">Open login</a>
656
+ </article>
657
+ <article class="card" data-reveal>
658
+ <h3>Usage + jobs</h3>
659
+ <p>View usage summaries and recent jobs.</p>
660
+ <a class="text-link" href="usage.html">Open usage</a>
661
+ </article>
662
+ <article class="card" data-reveal>
663
+ <h3>API tokens</h3>
664
+ <p>Create and manage tokens for CLI and SDK access.</p>
665
+ <a class="text-link" href="tokens.html">Open tokens</a>
666
+ </article>
667
+ <article class="card" data-reveal>
668
+ <h3>Claude skill</h3>
669
+ <p>Install the Claude Code skill for microVM runs.</p>
670
+ <a class="text-link" href="claude-skill.html">Open Claude skill</a>
671
+ </article>
672
+ <article class="card" data-reveal>
673
+ <h3>Cookbook</h3>
674
+ <p>Copy ready-to-run recipes for common workflows.</p>
675
+ <a class="text-link" href="cookbook.html">Open cookbook</a>
676
+ </article>
677
+ </div>
678
+ </div>
679
+ </section>
680
+ </main>
681
+
682
+ <footer class="site-footer">
683
+ <div class="container footer-grid">
684
+ <div>
685
+ <div class="brand">
686
+ <span class="logo">Mags</span>
687
+ <span class="tag">Instant sandboxes and runtime environments</span>
688
+ </div>
689
+ <p>Build, test, and run from clean microVMs whenever you need them.</p>
690
+ </div>
691
+ <div class="footer-links">
692
+ <a href="login.html">Login</a>
693
+ <a href="usage.html">Usage</a>
694
+ <a href="tokens.html">Tokens</a>
695
+ <a href="https://pypi.org/project/magpie-mags/" rel="noreferrer">Python SDK</a>
696
+ <a href="https://www.npmjs.com/package/@magpiecloud/mags" rel="noreferrer">Node.js SDK</a>
697
+ <a href="api.html">API Reference</a>
698
+ <a href="claude-skill.html">Claude Skill</a>
699
+ <a href="cookbook.html">Cookbook</a>
700
+ <a href="https://github.com/magpiecloud/mags/issues" rel="noreferrer">Issues</a>
701
+ </div>
702
+ </div>
703
+ </footer>
704
+
705
+ <script src="script.js?v=2"></script>
706
+ <script>
707
+ (function() {
708
+ var token = localStorage.getItem('microvm-access-token');
709
+ if (token) {
710
+ var nav = document.getElementById('nav-auth-link');
711
+ var cta = document.getElementById('cta-auth-link');
712
+ if (nav) { nav.textContent = 'Dashboard'; nav.href = '/dashboard'; }
713
+ if (cta) { cta.textContent = 'Dashboard'; cta.href = '/dashboard'; }
714
+ }
715
+ })();
716
+ </script>
717
+ </body>
718
+ </html>