@magpiecloud/mags 1.8.13 → 1.8.14

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.
@@ -0,0 +1,677 @@
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 Docs</title>
7
+ <meta
8
+ name="description"
9
+ content="Complete documentation for Mags — secure cloud sandboxes for AI agents and developers. CLI, Python SDK, Node.js SDK, REST API."
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=8" />
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
+ <a href="index.html"><span class="logo">Mags</span></a>
23
+ <span class="tag">Documentation</span>
24
+ </div>
25
+ <nav class="nav-links">
26
+ <a href="index.html">Home</a>
27
+ <a href="api.html">API</a>
28
+ <a href="cookbook.html">Cookbook</a>
29
+ <a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
30
+ <a href="login.html" id="nav-auth-link">Login</a>
31
+ </nav>
32
+ <div class="nav-cta">
33
+ <a class="button ghost" href="login.html" id="cta-auth-link">Login</a>
34
+ </div>
35
+ </div>
36
+ </header>
37
+
38
+ <div class="docs-layout">
39
+ <!-- ── Sidebar ─────────────────────────────────────── -->
40
+ <aside class="docs-sidebar">
41
+ <nav class="docs-nav">
42
+ <div class="docs-nav-group">
43
+ <p class="docs-nav-heading">Getting Started</p>
44
+ <a class="docs-nav-link active" href="#overview">Overview</a>
45
+ <a class="docs-nav-link" href="#quickstart">Quickstart</a>
46
+ <a class="docs-nav-link" href="#authentication">Authentication</a>
47
+ </div>
48
+ <div class="docs-nav-group">
49
+ <p class="docs-nav-heading">CLI Reference</p>
50
+ <a class="docs-nav-link" href="#cli-run">Running Scripts</a>
51
+ <a class="docs-nav-link" href="#cli-sandboxes">Sandboxes</a>
52
+ <a class="docs-nav-link" href="#cli-management">Management</a>
53
+ <a class="docs-nav-link" href="#cli-flags">Flags</a>
54
+ <a class="docs-nav-link" href="#cli-examples">Examples</a>
55
+ </div>
56
+ <div class="docs-nav-group">
57
+ <p class="docs-nav-heading">Concepts</p>
58
+ <a class="docs-nav-link" href="#workspaces">Workspaces</a>
59
+ <a class="docs-nav-link" href="#always-on">Always-On Servers</a>
60
+ <a class="docs-nav-link" href="#public-urls">Public URLs</a>
61
+ <a class="docs-nav-link" href="#cron">Cron Jobs</a>
62
+ <a class="docs-nav-link" href="#file-upload">File Upload</a>
63
+ </div>
64
+ <div class="docs-nav-group">
65
+ <p class="docs-nav-heading">Python SDK</p>
66
+ <a class="docs-nav-link" href="#python-install">Install</a>
67
+ <a class="docs-nav-link" href="#python-methods">Methods</a>
68
+ <a class="docs-nav-link" href="#python-options">Run Options</a>
69
+ <a class="docs-nav-link" href="#python-examples">Examples</a>
70
+ </div>
71
+ <div class="docs-nav-group">
72
+ <p class="docs-nav-heading">Node.js SDK</p>
73
+ <a class="docs-nav-link" href="#node-install">Install</a>
74
+ <a class="docs-nav-link" href="#node-methods">Methods</a>
75
+ <a class="docs-nav-link" href="#node-options">Run Options</a>
76
+ <a class="docs-nav-link" href="#node-examples">Examples</a>
77
+ </div>
78
+ <div class="docs-nav-group">
79
+ <p class="docs-nav-heading">More</p>
80
+ <a class="docs-nav-link" href="api.html">REST API Reference</a>
81
+ <a class="docs-nav-link" href="cookbook.html">Cookbook</a>
82
+ <a class="docs-nav-link" href="claude-skill.html">Claude Skill</a>
83
+ </div>
84
+ </nav>
85
+ </aside>
86
+
87
+ <!-- ── Content ─────────────────────────────────────── -->
88
+ <main class="docs-content">
89
+
90
+ <!-- Overview -->
91
+ <section class="docs-section" id="overview">
92
+ <h1>Mags Documentation</h1>
93
+ <p class="lead">
94
+ Mags gives AI agents and developers secure, isolated sandboxes that boot in ~300ms.
95
+ Workspaces persist automatically to the cloud. Run code, schedule jobs, and let agents work safely.
96
+ </p>
97
+ <p>Available via <strong>CLI</strong>, <strong>Python SDK</strong>, <strong>Node.js SDK</strong>, and <strong>REST API</strong>.</p>
98
+ <pre><code>npm install -g @magpiecloud/mags
99
+ mags run 'echo Hello World'</code></pre>
100
+ </section>
101
+
102
+ <!-- Quickstart -->
103
+ <section class="docs-section" id="quickstart">
104
+ <h2>Quickstart</h2>
105
+ <p>Get your first sandbox running in under a minute.</p>
106
+
107
+ <h3>1. Install the CLI</h3>
108
+ <pre><code>npm install -g @magpiecloud/mags</code></pre>
109
+
110
+ <h3>2. Authenticate</h3>
111
+ <pre><code>mags login</code></pre>
112
+ <p>Or set a token directly:</p>
113
+ <pre><code>export MAGS_API_TOKEN="your-token"</code></pre>
114
+
115
+ <h3>3. Run a command</h3>
116
+ <pre><code>mags run 'echo Hello World'</code></pre>
117
+ <p>Add <code>-w myproject -p</code> to persist files to the cloud between runs.</p>
118
+ </section>
119
+
120
+ <!-- Authentication -->
121
+ <section class="docs-section" id="authentication">
122
+ <h2>Authentication</h2>
123
+ <p>There are two ways to authenticate with Mags.</p>
124
+
125
+ <h3>Browser login</h3>
126
+ <pre><code>mags login</code></pre>
127
+ <p>Opens a browser for Google sign-in. Credentials are stored in <code>~/.mags/config.json</code>.</p>
128
+
129
+ <h3>API token</h3>
130
+ <pre><code>export MAGS_API_TOKEN="your-token"</code></pre>
131
+ <p>Generate tokens from the <a class="text-link" href="tokens.html">token dashboard</a>. Tokens are shown once when created &mdash; store them securely.</p>
132
+ <p>The environment variable takes precedence over <code>mags login</code> credentials.</p>
133
+ </section>
134
+
135
+ <!-- CLI: Running Scripts -->
136
+ <section class="docs-section" id="cli-run">
137
+ <h2>Running Scripts</h2>
138
+ <p>The <code>mags run</code> command submits a script to run in a fresh sandbox.</p>
139
+
140
+ <div class="ref-table-wrap">
141
+ <table class="ref-table">
142
+ <thead><tr><th>Command</th><th>Description</th></tr></thead>
143
+ <tbody>
144
+ <tr><td><code>mags run &lt;script&gt;</code></td><td>Run a script in a fresh sandbox. Fastest &mdash; no workspace, no persistence.</td></tr>
145
+ <tr><td><code>mags run -w &lt;name&gt; &lt;script&gt;</code></td><td>Run with a named workspace. Data stays on the VM only &mdash; deleted after 10 min idle.</td></tr>
146
+ <tr><td><code>mags run -w &lt;name&gt; -p &lt;script&gt;</code></td><td>Run with a persistent workspace. Files synced to S3 and survive across runs indefinitely.</td></tr>
147
+ <tr><td><code>mags run --url --port &lt;port&gt; &lt;script&gt;</code></td><td>Request a public HTTPS URL for your sandbox (requires <code>-p</code>).</td></tr>
148
+ <tr><td><code>mags run --no-sleep &lt;script&gt;</code></td><td>Keep sandbox running 24/7, never auto-sleep (requires <code>-p</code>).</td></tr>
149
+ <tr><td><code>mags run -e &lt;script&gt;</code></td><td>Ephemeral &mdash; no workspace at all, fastest possible execution.</td></tr>
150
+ <tr><td><code>mags run --base &lt;workspace&gt; &lt;script&gt;</code></td><td>Use an existing workspace as a read-only base image (OverlayFS).</td></tr>
151
+ <tr><td><code>mags run -f &lt;file&gt; &lt;script&gt;</code></td><td>Upload file(s) into the sandbox before running (repeatable).</td></tr>
152
+ </tbody>
153
+ </table>
154
+ </div>
155
+ </section>
156
+
157
+ <!-- CLI: Sandboxes -->
158
+ <section class="docs-section" id="cli-sandboxes">
159
+ <h2>Sandboxes</h2>
160
+ <p>Create long-lived sandboxes you can SSH into and run commands on.</p>
161
+
162
+ <div class="ref-table-wrap">
163
+ <table class="ref-table">
164
+ <thead><tr><th>Command</th><th>Description</th></tr></thead>
165
+ <tbody>
166
+ <tr><td><code>mags new &lt;name&gt;</code></td><td>Create a new sandbox. Workspace lives on local disk only.</td></tr>
167
+ <tr><td><code>mags new &lt;name&gt; -p</code></td><td>Create a sandbox with persistent workspace &mdash; synced to S3.</td></tr>
168
+ <tr><td><code>mags exec &lt;name&gt; &lt;cmd&gt;</code></td><td>Execute a command on an existing sandbox.</td></tr>
169
+ <tr><td><code>mags ssh &lt;name&gt;</code></td><td>SSH into a sandbox. Auto-starts if sleeping or stopped.</td></tr>
170
+ </tbody>
171
+ </table>
172
+ </div>
173
+ </section>
174
+
175
+ <!-- CLI: Management -->
176
+ <section class="docs-section" id="cli-management">
177
+ <h2>Management</h2>
178
+ <p>Commands for listing, inspecting, and controlling jobs and workspaces.</p>
179
+
180
+ <div class="ref-table-wrap">
181
+ <table class="ref-table">
182
+ <thead><tr><th>Command</th><th>Description</th></tr></thead>
183
+ <tbody>
184
+ <tr><td><code>mags list</code></td><td>List recent jobs</td></tr>
185
+ <tr><td><code>mags status &lt;id&gt;</code></td><td>Get job status</td></tr>
186
+ <tr><td><code>mags logs &lt;id&gt;</code></td><td>Get job output</td></tr>
187
+ <tr><td><code>mags stop &lt;id&gt;</code></td><td>Stop a running job</td></tr>
188
+ <tr><td><code>mags set &lt;id&gt; [options]</code></td><td>Update VM settings (e.g. <code>--no-sleep</code>, <code>--sleep</code>)</td></tr>
189
+ <tr><td><code>mags sync &lt;workspace&gt;</code></td><td>Sync workspace to the cloud now</td></tr>
190
+ <tr><td><code>mags url &lt;id&gt; [port]</code></td><td>Enable public URL access</td></tr>
191
+ <tr><td><code>mags resize &lt;workspace&gt; --disk &lt;GB&gt;</code></td><td>Resize workspace disk</td></tr>
192
+ <tr><td><code>mags workspace list</code></td><td>List persistent workspaces</td></tr>
193
+ <tr><td><code>mags workspace delete &lt;id&gt;</code></td><td>Delete workspace + cloud data</td></tr>
194
+ <tr><td><code>mags url alias &lt;sub&gt; &lt;workspace&gt;</code></td><td>Create a stable URL alias</td></tr>
195
+ <tr><td><code>mags url alias list</code></td><td>List URL aliases</td></tr>
196
+ <tr><td><code>mags url alias remove &lt;sub&gt;</code></td><td>Delete a URL alias</td></tr>
197
+ <tr><td><code>mags cron add [opts] &lt;script&gt;</code></td><td>Create a scheduled cron job</td></tr>
198
+ <tr><td><code>mags cron list</code></td><td>List cron jobs</td></tr>
199
+ <tr><td><code>mags cron enable &lt;id&gt;</code></td><td>Enable a cron job</td></tr>
200
+ <tr><td><code>mags cron disable &lt;id&gt;</code></td><td>Disable a cron job</td></tr>
201
+ <tr><td><code>mags cron remove &lt;id&gt;</code></td><td>Delete a cron job</td></tr>
202
+ </tbody>
203
+ </table>
204
+ </div>
205
+ </section>
206
+
207
+ <!-- CLI: Flags -->
208
+ <section class="docs-section" id="cli-flags">
209
+ <h2>Flags</h2>
210
+ <p>Additional flags available on <code>mags run</code> and <code>mags new</code>.</p>
211
+
212
+ <div class="ref-table-wrap">
213
+ <table class="ref-table">
214
+ <thead><tr><th>Flag</th><th>Description</th></tr></thead>
215
+ <tbody>
216
+ <tr><td><code>-w, --workspace &lt;name&gt;</code></td><td>Name the workspace. Local only unless <code>-p</code> is also set.</td></tr>
217
+ <tr><td><code>-p, --persistent</code></td><td>Sync workspace to S3. Files persist indefinitely.</td></tr>
218
+ <tr><td><code>-n, --name &lt;name&gt;</code></td><td>Alias for <code>-w</code></td></tr>
219
+ <tr><td><code>-e, --ephemeral</code></td><td>No workspace at all, fastest possible execution</td></tr>
220
+ <tr><td><code>--base &lt;workspace&gt;</code></td><td>Use an existing workspace as a read-only base image</td></tr>
221
+ <tr><td><code>--disk &lt;GB&gt;</code></td><td>Custom disk size in GB (default: 2)</td></tr>
222
+ <tr><td><code>--no-sleep</code></td><td>Never auto-sleep (requires <code>-p</code>)</td></tr>
223
+ <tr><td><code>--url</code></td><td>Request a public HTTPS URL</td></tr>
224
+ <tr><td><code>--port &lt;port&gt;</code></td><td>Port to expose via public URL</td></tr>
225
+ <tr><td><code>-f, --file &lt;path&gt;</code></td><td>Upload file(s) into sandbox before running (repeatable)</td></tr>
226
+ <tr><td><code>--startup-command &lt;cmd&gt;</code></td><td>Command to run when sandbox wakes from sleep</td></tr>
227
+ </tbody>
228
+ </table>
229
+ </div>
230
+ </section>
231
+
232
+ <!-- CLI: Examples -->
233
+ <section class="docs-section" id="cli-examples">
234
+ <h2>CLI Examples</h2>
235
+ <pre><code># Persistent workspace &mdash; install packages, then run your app
236
+ mags run -w myproject -p 'pip install flask requests'
237
+ mags run -w myproject -p 'python3 app.py'
238
+
239
+ # Golden image &mdash; create once, fork many times
240
+ mags run -w golden -p 'apk add nodejs npm && npm install -g typescript'
241
+ mags sync golden
242
+ mags run --base golden -w fork-1 -p 'npm test'
243
+
244
+ # Interactive sandbox with SSH
245
+ mags new dev -p
246
+ mags ssh dev
247
+ mags exec dev 'node --version'
248
+
249
+ # Always-on web server with public URL
250
+ mags run -w webapp -p --no-sleep --url --port 8080 \
251
+ --startup-command 'python3 -m http.server 8080' \
252
+ 'python3 -m http.server 8080'
253
+
254
+ # Cron job
255
+ mags cron add --name backup --schedule "0 0 * * *" \
256
+ -w backups 'tar czf backup.tar.gz /data'</code></pre>
257
+ </section>
258
+
259
+ <!-- Workspaces -->
260
+ <section class="docs-section" id="workspaces">
261
+ <h2>Workspaces</h2>
262
+ <p>Workspaces let you keep files between sandbox runs. There are two modes:</p>
263
+
264
+ <h3>Local workspace (<code>-w</code>)</h3>
265
+ <p>Data stays on the VM only. Good for throwaway analysis or short-lived tasks. Deleted after 10 minutes of idle.</p>
266
+ <pre><code>mags run -w analysis 'python3 analyze.py'</code></pre>
267
+
268
+ <h3>Persistent workspace (<code>-w -p</code>)</h3>
269
+ <p>Files, packages, and configs are synced to S3 and survive across runs indefinitely. Survives reboots, sleep, and agent restarts.</p>
270
+ <pre><code>mags run -w myproject -p 'pip install flask'
271
+ mags run -w myproject -p 'python3 app.py'</code></pre>
272
+
273
+ <h3>Base images (<code>--base</code>)</h3>
274
+ <p>Clone a persistent workspace as a read-only base for new sandboxes using OverlayFS. The base is never modified &mdash; writes go to the overlay.</p>
275
+ <pre><code># Create a golden image
276
+ mags run -w golden -p 'apk add nodejs npm && npm install -g typescript'
277
+ mags sync golden
278
+
279
+ # Fork from the golden image
280
+ mags run --base golden -w fork-1 -p 'npm test'</code></pre>
281
+
282
+ <h3>Isolation</h3>
283
+ <ul class="list">
284
+ <li>Every sandbox runs in its own isolated environment</li>
285
+ <li>No cross-user access &mdash; workspaces are private</li>
286
+ <li>Processes, memory, and ports reset between runs</li>
287
+ <li>Agents can't escape or affect the host</li>
288
+ </ul>
289
+
290
+ <h3>Managing workspaces</h3>
291
+ <pre><code>mags workspace list # List persistent workspaces
292
+ mags workspace delete myproject # Delete workspace + cloud data
293
+ mags sync myproject # Force sync to S3 now
294
+ mags resize myproject --disk 5 # Resize to 5 GB</code></pre>
295
+ </section>
296
+
297
+ <!-- Always-On Servers -->
298
+ <section class="docs-section" id="always-on">
299
+ <h2>Always-On Servers</h2>
300
+ <p>By default, persistent sandboxes auto-sleep after 10 minutes of inactivity. With the <code>--no-sleep</code> flag, your VM stays running 24/7 &mdash; perfect for web servers, workers, and background processes.</p>
301
+
302
+ <pre><code># CLI
303
+ mags run -w my-api -p --no-sleep --url --port 3000 'node server.js'
304
+
305
+ # Python
306
+ m.run("node server.js",
307
+ workspace_id="my-api", persistent=True, no_sleep=True)
308
+
309
+ # Node.js
310
+ await mags.run('node server.js', {
311
+ workspaceId: 'my-api', persistent: true, noSleep: true,
312
+ });</code></pre>
313
+
314
+ <h3>Auto-recovery</h3>
315
+ <p>Always-on sandboxes are automatically monitored. If the host goes down, your VM is re-provisioned on a healthy server within ~60 seconds &mdash; no manual intervention needed.</p>
316
+
317
+ <h3>Requirements</h3>
318
+ <ul class="list">
319
+ <li>Requires <code>-p</code> (persistent) flag</li>
320
+ <li>VM stays in <code>running</code> state indefinitely</li>
321
+ <li>Combine with <code>--url</code> to expose a public HTTPS endpoint</li>
322
+ <li>Use <code>--startup-command</code> to auto-restart your process if the VM recovers</li>
323
+ <li>Files persist to the cloud via workspace sync</li>
324
+ </ul>
325
+ </section>
326
+
327
+ <!-- Public URLs -->
328
+ <section class="docs-section" id="public-urls">
329
+ <h2>Public URLs</h2>
330
+ <p>Expose any port on your sandbox as a public HTTPS URL.</p>
331
+
332
+ <pre><code># Enable URL when running
333
+ mags run -w webapp -p --url --port 8080 'python3 -m http.server 8080'
334
+
335
+ # Enable URL on an existing job
336
+ mags url &lt;job-id&gt; 8080</code></pre>
337
+
338
+ <h3>URL aliases</h3>
339
+ <p>Create stable, human-readable subdomains that point to a workspace.</p>
340
+ <pre><code>mags url alias myapp webapp # myapp.apps.magpiecloud.com
341
+ mags url alias list # List all aliases
342
+ mags url alias remove myapp # Delete alias</code></pre>
343
+ </section>
344
+
345
+ <!-- Cron Jobs -->
346
+ <section class="docs-section" id="cron">
347
+ <h2>Cron Jobs</h2>
348
+ <p>Schedule scripts to run on a recurring basis.</p>
349
+
350
+ <pre><code># Create a cron job
351
+ mags cron add --name backup --schedule "0 0 * * *" \
352
+ -w backups 'tar czf backup.tar.gz /data'
353
+
354
+ # Manage cron jobs
355
+ mags cron list
356
+ mags cron enable &lt;id&gt;
357
+ mags cron disable &lt;id&gt;
358
+ mags cron remove &lt;id&gt;</code></pre>
359
+
360
+ <p>Cron expressions use standard 5-field format: <code>minute hour day month weekday</code>.</p>
361
+ </section>
362
+
363
+ <!-- File Upload -->
364
+ <section class="docs-section" id="file-upload">
365
+ <h2>File Upload</h2>
366
+ <p>Upload local files into a sandbox before running your script.</p>
367
+
368
+ <pre><code># CLI &mdash; use -f (repeatable)
369
+ mags run -f script.py -f data.csv 'python3 /uploads/script.py'
370
+
371
+ # Python
372
+ file_ids = m.upload_files(["script.py", "data.csv"])
373
+ m.run_and_wait("python3 /uploads/script.py", file_ids=file_ids)
374
+
375
+ # Node.js
376
+ const fileId = await mags.uploadFile('script.py');
377
+ await mags.runAndWait('python3 /uploads/script.py', { fileIds: [fileId] });</code></pre>
378
+
379
+ <p>Uploaded files are placed in <code>/uploads/</code> inside the sandbox.</p>
380
+ </section>
381
+
382
+ <!-- Python SDK: Install -->
383
+ <section class="docs-section" id="python-install">
384
+ <h2>Python SDK</h2>
385
+ <pre><code>pip install magpie-mags
386
+ export MAGS_API_TOKEN="your-token"</code></pre>
387
+ <p>Or pass the token directly: <code>Mags(api_token="...")</code></p>
388
+ <p><a class="text-link" href="https://pypi.org/project/magpie-mags/" rel="noreferrer">View on PyPI &rarr;</a></p>
389
+ </section>
390
+
391
+ <!-- Python SDK: Methods -->
392
+ <section class="docs-section" id="python-methods">
393
+ <h2>Python Methods</h2>
394
+ <div class="ref-table-wrap">
395
+ <table class="ref-table">
396
+ <thead><tr><th>Method</th><th>Description</th></tr></thead>
397
+ <tbody>
398
+ <tr><td><code>run(script, **opts)</code></td><td>Submit a job (returns immediately)</td></tr>
399
+ <tr><td><code>run_and_wait(script, **opts)</code></td><td>Submit + block until complete</td></tr>
400
+ <tr><td><code>new(name, **opts)</code></td><td>Create VM sandbox (pass <code>persistent=True</code> for S3)</td></tr>
401
+ <tr><td><code>exec(name, command)</code></td><td>Run command on existing sandbox via SSH</td></tr>
402
+ <tr><td><code>stop(name_or_id)</code></td><td>Stop a running job</td></tr>
403
+ <tr><td><code>find_job(name_or_id)</code></td><td>Find job by name or workspace</td></tr>
404
+ <tr><td><code>url(name_or_id, port)</code></td><td>Enable public URL access</td></tr>
405
+ <tr><td><code>resize(workspace, disk_gb)</code></td><td>Resize workspace disk</td></tr>
406
+ <tr><td><code>status(request_id)</code></td><td>Get job status</td></tr>
407
+ <tr><td><code>logs(request_id)</code></td><td>Get job logs</td></tr>
408
+ <tr><td><code>list_jobs()</code></td><td>List recent jobs</td></tr>
409
+ <tr><td><code>update_job(request_id, **opts)</code></td><td>Update job settings (<code>no_sleep</code>, <code>startup_command</code>)</td></tr>
410
+ <tr><td><code>enable_access(id, port)</code></td><td>Enable URL or SSH access (low-level)</td></tr>
411
+ <tr><td><code>upload_file(path)</code></td><td>Upload a file, returns file ID</td></tr>
412
+ <tr><td><code>upload_files(paths)</code></td><td>Upload files, returns file IDs</td></tr>
413
+ <tr><td><code>list_workspaces()</code></td><td>List persistent workspaces</td></tr>
414
+ <tr><td><code>delete_workspace(id)</code></td><td>Delete workspace + cloud data</td></tr>
415
+ <tr><td><code>sync(request_id)</code></td><td>Sync workspace to S3 now</td></tr>
416
+ <tr><td><code>url_alias_create(sub, ws_id)</code></td><td>Create a stable URL alias</td></tr>
417
+ <tr><td><code>url_alias_list()</code></td><td>List URL aliases</td></tr>
418
+ <tr><td><code>url_alias_delete(sub)</code></td><td>Delete a URL alias</td></tr>
419
+ <tr><td><code>cron_create(**opts)</code></td><td>Create a cron job</td></tr>
420
+ <tr><td><code>cron_list()</code></td><td>List cron jobs</td></tr>
421
+ <tr><td><code>cron_update(id, **opts)</code></td><td>Update a cron job</td></tr>
422
+ <tr><td><code>cron_delete(id)</code></td><td>Delete a cron job</td></tr>
423
+ <tr><td><code>usage(window_days)</code></td><td>Get usage stats</td></tr>
424
+ </tbody>
425
+ </table>
426
+ </div>
427
+ </section>
428
+
429
+ <!-- Python SDK: Run Options -->
430
+ <section class="docs-section" id="python-options">
431
+ <h2>Python Run Options</h2>
432
+ <div class="ref-table-wrap">
433
+ <table class="ref-table">
434
+ <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
435
+ <tbody>
436
+ <tr><td><code>workspace_id</code></td><td>Name the workspace. Local only unless <code>persistent=True</code>.</td></tr>
437
+ <tr><td><code>persistent</code></td><td>Keep sandbox alive, sync workspace to S3. Files persist indefinitely.</td></tr>
438
+ <tr><td><code>base_workspace_id</code></td><td>Mount a workspace read-only as base image</td></tr>
439
+ <tr><td><code>no_sleep</code></td><td>Never auto-sleep (requires <code>persistent=True</code>)</td></tr>
440
+ <tr><td><code>ephemeral</code></td><td>No workspace, no sync (fastest)</td></tr>
441
+ <tr><td><code>file_ids</code></td><td>List of uploaded file IDs to include</td></tr>
442
+ <tr><td><code>startup_command</code></td><td>Command to run when sandbox wakes</td></tr>
443
+ </tbody>
444
+ </table>
445
+ </div>
446
+ </section>
447
+
448
+ <!-- Python SDK: Examples -->
449
+ <section class="docs-section" id="python-examples">
450
+ <h2>Python Examples</h2>
451
+ <pre><code>from mags import Mags
452
+ m = Mags() # reads MAGS_API_TOKEN from env
453
+
454
+ # Run a command and wait
455
+ result = m.run_and_wait("echo Hello World")
456
+ print(result["status"]) # "completed"
457
+
458
+ # Local workspace (no S3 sync, good for analysis)
459
+ m.run_and_wait("python3 analyze.py", workspace_id="analysis")
460
+
461
+ # Persistent workspace (synced to S3)
462
+ m.run("pip install flask",
463
+ workspace_id="my-project", persistent=True)
464
+
465
+ # Create a sandbox (local disk)
466
+ m.new("my-project")
467
+
468
+ # Create with S3 persistence
469
+ m.new("my-project", persistent=True)
470
+
471
+ # Execute commands on existing sandbox
472
+ result = m.exec("my-project", "ls -la /root")
473
+ print(result["output"])
474
+
475
+ # Public URL
476
+ m.new("webapp", persistent=True)
477
+ info = m.url("webapp", port=3000)
478
+ print(info["url"]) # https://xyz.apps.magpiecloud.com
479
+
480
+ # Always-on sandbox (never auto-sleeps)
481
+ m.run("python3 worker.py",
482
+ workspace_id="worker", persistent=True, no_sleep=True)
483
+
484
+ # Upload files
485
+ file_ids = m.upload_files(["script.py", "data.csv"])
486
+ m.run_and_wait("python3 /uploads/script.py", file_ids=file_ids)
487
+
488
+ # Workspaces
489
+ workspaces = m.list_workspaces()
490
+ m.delete_workspace("myproject")
491
+
492
+ # Cron
493
+ m.cron_create(name="backup", cron_expression="0 0 * * *",
494
+ script="tar czf backup.tar.gz /data", workspace_id="backups")</code></pre>
495
+ </section>
496
+
497
+ <!-- Node.js SDK: Install -->
498
+ <section class="docs-section" id="node-install">
499
+ <h2>Node.js SDK</h2>
500
+ <pre><code>npm install @magpiecloud/mags
501
+ export MAGS_API_TOKEN="your-token"</code></pre>
502
+ <p>Or pass the token directly: <code>new Mags({ apiToken: "..." })</code></p>
503
+ <p><a class="text-link" href="https://www.npmjs.com/package/@magpiecloud/mags" rel="noreferrer">View on npm &rarr;</a></p>
504
+ </section>
505
+
506
+ <!-- Node.js SDK: Methods -->
507
+ <section class="docs-section" id="node-methods">
508
+ <h2>Node.js Methods</h2>
509
+ <div class="ref-table-wrap">
510
+ <table class="ref-table">
511
+ <thead><tr><th>Method</th><th>Description</th></tr></thead>
512
+ <tbody>
513
+ <tr><td><code>run(script, opts)</code></td><td>Submit a job (returns immediately)</td></tr>
514
+ <tr><td><code>runAndWait(script, opts)</code></td><td>Submit + block until complete</td></tr>
515
+ <tr><td><code>new(name, opts)</code></td><td>Create a VM sandbox (add <code>persistent: true</code> for S3)</td></tr>
516
+ <tr><td><code>exec(nameOrId, command)</code></td><td>Run command on existing sandbox via SSH</td></tr>
517
+ <tr><td><code>stop(nameOrId)</code></td><td>Stop a running job</td></tr>
518
+ <tr><td><code>findJob(nameOrId)</code></td><td>Find job by name or workspace</td></tr>
519
+ <tr><td><code>url(nameOrId, port)</code></td><td>Enable public URL access</td></tr>
520
+ <tr><td><code>status(requestId)</code></td><td>Get job status</td></tr>
521
+ <tr><td><code>logs(requestId)</code></td><td>Get job logs</td></tr>
522
+ <tr><td><code>list()</code></td><td>List recent jobs</td></tr>
523
+ <tr><td><code>updateJob(requestId, opts)</code></td><td>Update job settings (<code>noSleep</code>, <code>startupCommand</code>)</td></tr>
524
+ <tr><td><code>enableAccess(requestId, port)</code></td><td>Enable URL or SSH access</td></tr>
525
+ <tr><td><code>resize(workspace, diskGb)</code></td><td>Resize workspace disk</td></tr>
526
+ <tr><td><code>uploadFiles(paths)</code></td><td>Upload files, returns file IDs</td></tr>
527
+ <tr><td><code>sync(requestId)</code></td><td>Sync workspace to S3 now</td></tr>
528
+ <tr><td><code>listWorkspaces()</code></td><td>List persistent workspaces</td></tr>
529
+ <tr><td><code>deleteWorkspace(id)</code></td><td>Delete workspace + cloud data</td></tr>
530
+ <tr><td><code>urlAliasCreate(sub, wsId)</code></td><td>Create a stable URL alias</td></tr>
531
+ <tr><td><code>urlAliasList()</code></td><td>List URL aliases</td></tr>
532
+ <tr><td><code>urlAliasDelete(sub)</code></td><td>Delete a URL alias</td></tr>
533
+ <tr><td><code>cronCreate(opts)</code></td><td>Create a cron job</td></tr>
534
+ <tr><td><code>cronList()</code></td><td>List cron jobs</td></tr>
535
+ <tr><td><code>cronDelete(id)</code></td><td>Delete a cron job</td></tr>
536
+ <tr><td><code>usage(opts)</code></td><td>Get usage stats</td></tr>
537
+ </tbody>
538
+ </table>
539
+ </div>
540
+ </section>
541
+
542
+ <!-- Node.js SDK: Run Options -->
543
+ <section class="docs-section" id="node-options">
544
+ <h2>Node.js Run Options</h2>
545
+ <div class="ref-table-wrap">
546
+ <table class="ref-table">
547
+ <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
548
+ <tbody>
549
+ <tr><td><code>workspaceId</code></td><td>Name the workspace. Local only unless <code>persistent: true</code>.</td></tr>
550
+ <tr><td><code>persistent</code></td><td>Keep sandbox alive, sync workspace to S3. Files persist indefinitely.</td></tr>
551
+ <tr><td><code>baseWorkspaceId</code></td><td>Mount a workspace read-only as base image</td></tr>
552
+ <tr><td><code>noSleep</code></td><td>Never auto-sleep (requires <code>persistent: true</code>)</td></tr>
553
+ <tr><td><code>ephemeral</code></td><td>No workspace, no sync (fastest)</td></tr>
554
+ <tr><td><code>fileIds</code></td><td>Array of uploaded file IDs to include</td></tr>
555
+ <tr><td><code>startupCommand</code></td><td>Command to run when sandbox wakes</td></tr>
556
+ </tbody>
557
+ </table>
558
+ </div>
559
+ </section>
560
+
561
+ <!-- Node.js SDK: Examples -->
562
+ <section class="docs-section" id="node-examples">
563
+ <h2>Node.js Examples</h2>
564
+ <pre><code>const Mags = require('@magpiecloud/mags');
565
+ const mags = new Mags({ apiToken: process.env.MAGS_API_TOKEN });
566
+
567
+ // Run a command and wait
568
+ const result = await mags.runAndWait('echo Hello World');
569
+ console.log(result.status); // "completed"
570
+
571
+ // Local workspace (no S3 sync, good for analysis)
572
+ await mags.runAndWait('python3 analyze.py', { workspaceId: 'analysis' });
573
+
574
+ // Persistent workspace (synced to S3)
575
+ await mags.runAndWait('pip install flask', { workspaceId: 'myproject', persistent: true });
576
+ await mags.runAndWait('python3 app.py', { workspaceId: 'myproject', persistent: true });
577
+
578
+ // Base image
579
+ await mags.runAndWait('npm test', { baseWorkspaceId: 'golden' });
580
+ await mags.runAndWait('npm test', { baseWorkspaceId: 'golden', workspaceId: 'fork-1', persistent: true });
581
+
582
+ // Create a sandbox
583
+ await mags.new('dev', { persistent: true });
584
+
585
+ // SSH access
586
+ const job = await mags.run('sleep 3600', { workspaceId: 'dev', persistent: true });
587
+ const ssh = await mags.enableAccess(job.requestId, 22);
588
+ console.log(`ssh root@${ssh.sshHost} -p ${ssh.sshPort}`);
589
+
590
+ // Public URL
591
+ const webJob = await mags.run('python3 -m http.server 8080', {
592
+ workspaceId: 'webapp', persistent: true,
593
+ startupCommand: 'python3 -m http.server 8080',
594
+ });
595
+ const { url } = await mags.url('webapp', 8080);
596
+ console.log(url);
597
+
598
+ // Always-on sandbox (never auto-sleeps)
599
+ await mags.run('python3 worker.py', {
600
+ workspaceId: 'worker', persistent: true, noSleep: true,
601
+ });
602
+
603
+ // Upload files
604
+ const fileId = await mags.uploadFile('script.py');
605
+ await mags.runAndWait('python3 /uploads/script.py', { fileIds: [fileId] });
606
+
607
+ // Cron
608
+ await mags.cronCreate({
609
+ name: 'backup', cronExpression: '0 0 * * *',
610
+ script: 'tar czf backup.tar.gz /data', workspaceId: 'backups',
611
+ });</code></pre>
612
+ </section>
613
+
614
+ </main>
615
+ </div>
616
+
617
+ <footer class="site-footer">
618
+ <div class="container footer-grid">
619
+ <div>
620
+ <div class="brand">
621
+ <span class="logo">Mags</span>
622
+ <span class="tag">Secure cloud sandboxes for the AI age</span>
623
+ </div>
624
+ <p>Secure, instant sandboxes for AI agents, developers, and automation.</p>
625
+ </div>
626
+ <div class="footer-links">
627
+ <a href="index.html">Home</a>
628
+ <a href="login.html">Login</a>
629
+ <a href="usage.html">Usage</a>
630
+ <a href="tokens.html">Tokens</a>
631
+ <a href="api.html">API Reference</a>
632
+ <a href="cookbook.html">Cookbook</a>
633
+ <a href="claude-skill.html">Claude Skill</a>
634
+ <a href="https://discord.gg/3avpC2nS" rel="noreferrer" target="_blank">Discord</a>
635
+ </div>
636
+ </div>
637
+ </footer>
638
+
639
+ <script src="script.js?v=8"></script>
640
+ <script>
641
+ (function() {
642
+ // Auth link swap
643
+ var token = localStorage.getItem('microvm-access-token');
644
+ if (token) {
645
+ var nav = document.getElementById('nav-auth-link');
646
+ var cta = document.getElementById('cta-auth-link');
647
+ if (nav) { nav.textContent = 'Usage'; nav.href = 'usage.html'; }
648
+ if (cta) { cta.textContent = 'Dashboard'; cta.href = 'usage.html'; }
649
+ }
650
+
651
+ // Active sidebar link on scroll
652
+ var links = document.querySelectorAll('.docs-nav-link[href^="#"]');
653
+ var sections = [];
654
+ links.forEach(function(link) {
655
+ var id = link.getAttribute('href').slice(1);
656
+ var el = document.getElementById(id);
657
+ if (el) sections.push({ id: id, el: el, link: link });
658
+ });
659
+
660
+ function updateActive() {
661
+ var scrollY = window.scrollY + 100;
662
+ var active = sections[0];
663
+ for (var i = 0; i < sections.length; i++) {
664
+ if (sections[i].el.offsetTop <= scrollY) {
665
+ active = sections[i];
666
+ }
667
+ }
668
+ links.forEach(function(l) { l.classList.remove('active'); });
669
+ if (active) active.link.classList.add('active');
670
+ }
671
+
672
+ window.addEventListener('scroll', updateActive, { passive: true });
673
+ updateActive();
674
+ })();
675
+ </script>
676
+ </body>
677
+ </html>