@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.
- package/API.md +381 -0
- package/Mags-API.postman_collection.json +374 -0
- package/QUICKSTART.md +283 -0
- package/README.md +287 -79
- package/bin/mags.js +151 -27
- package/deploy-page.sh +171 -0
- package/index.js +1 -163
- package/mags +0 -0
- package/mags.sh +270 -0
- package/nodejs/README.md +191 -0
- package/nodejs/bin/mags.js +1146 -0
- package/nodejs/index.js +326 -0
- package/nodejs/package.json +42 -0
- package/package.json +4 -15
- package/python/INTEGRATION.md +747 -0
- package/python/README.md +139 -0
- package/python/dist/magpie_mags-1.0.0-py3-none-any.whl +0 -0
- package/python/dist/magpie_mags-1.0.0.tar.gz +0 -0
- package/python/examples/demo.py +181 -0
- package/python/pyproject.toml +39 -0
- package/python/src/magpie_mags.egg-info/PKG-INFO +164 -0
- package/python/src/magpie_mags.egg-info/SOURCES.txt +9 -0
- package/python/src/magpie_mags.egg-info/dependency_links.txt +1 -0
- package/python/src/magpie_mags.egg-info/requires.txt +1 -0
- package/python/src/magpie_mags.egg-info/top_level.txt +1 -0
- package/python/src/mags/__init__.py +6 -0
- package/python/src/mags/client.py +283 -0
- package/skill.md +153 -0
- package/website/api.html +927 -0
- package/website/claude-skill.html +483 -0
- package/website/cookbook/hn-marketing.html +410 -0
- package/website/cookbook/hn-marketing.sh +50 -0
- package/website/cookbook.html +278 -0
- package/website/env.js +4 -0
- package/website/index.html +718 -0
- package/website/llms.txt +242 -0
- package/website/login.html +88 -0
- package/website/mags.md +171 -0
- package/website/script.js +425 -0
- package/website/styles.css +845 -0
- package/website/tokens.html +171 -0
- 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 — 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">▼</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">▼</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">▼</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">▼</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://<subdomain>.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">▼</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">▼</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 <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
|
+
|
|
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">▼</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">▼</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> — submit a job</li>
|
|
557
|
+
<li><code>run_and_wait(script, **opts)</code> — submit + block</li>
|
|
558
|
+
<li><code>status(request_id)</code> — get job status</li>
|
|
559
|
+
<li><code>logs(request_id)</code> — get job logs</li>
|
|
560
|
+
<li><code>list_jobs()</code> — list recent jobs</li>
|
|
561
|
+
<li><code>enable_access(id, port)</code> — URL or SSH</li>
|
|
562
|
+
<li><code>upload_files(paths)</code> — upload files</li>
|
|
563
|
+
<li><code>list_workspaces()</code> — list workspaces</li>
|
|
564
|
+
<li><code>delete_workspace(id)</code> — delete workspace</li>
|
|
565
|
+
<li><code>cron_create(**opts)</code> — create cron</li>
|
|
566
|
+
<li><code>cron_list()</code> / <code>cron_delete(id)</code></li>
|
|
567
|
+
<li><code>usage(window_days)</code> — 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 →</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> — submit a job</li>
|
|
593
|
+
<li><code>runAndWait(script, opts)</code> — submit + block</li>
|
|
594
|
+
<li><code>status(requestId)</code> — get job status</li>
|
|
595
|
+
<li><code>logs(requestId)</code> — get job logs</li>
|
|
596
|
+
<li><code>listJobs()</code> — list recent jobs</li>
|
|
597
|
+
<li><code>enableAccess(id, { port })</code> — URL or SSH</li>
|
|
598
|
+
<li><code>uploadFile(path)</code> — upload a file</li>
|
|
599
|
+
<li><code>cronCreate(opts)</code> — create cron</li>
|
|
600
|
+
<li><code>cronList()</code> / <code>cronDelete(id)</code></li>
|
|
601
|
+
<li><code>usage(windowDays)</code> — 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 →</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> — submit job</li>
|
|
624
|
+
<li><code>GET /mags-jobs</code> — list jobs</li>
|
|
625
|
+
<li><code>GET /mags-jobs/:id/status</code> — status</li>
|
|
626
|
+
<li><code>GET /mags-jobs/:id/logs</code> — logs</li>
|
|
627
|
+
<li><code>POST /mags-jobs/:id/access</code> — URL/SSH</li>
|
|
628
|
+
<li><code>PATCH /mags-jobs/:id</code> — update</li>
|
|
629
|
+
<li><code>POST /mags-files</code> — upload file</li>
|
|
630
|
+
<li><code>GET /mags-workspaces</code> — list ws</li>
|
|
631
|
+
<li><code>DELETE /mags-workspaces/:id</code> — delete ws</li>
|
|
632
|
+
<li><code>POST /mags-cron</code> — create cron</li>
|
|
633
|
+
<li><code>GET /mags-cron</code> — list cron</li>
|
|
634
|
+
<li><code>DELETE /mags-cron/:id</code> — delete cron</li>
|
|
635
|
+
</ul>
|
|
636
|
+
<p style="margin-top:1rem"><a class="text-link" href="api.html">Full API reference →</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>
|