@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
package/API.md
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# Mags API Reference
|
|
2
|
+
|
|
3
|
+
Base URL: `https://api.magpiecloud.com`
|
|
4
|
+
|
|
5
|
+
All endpoints require authentication via `Authorization: Bearer <token>` header.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Workflow: Base Image + Ephemeral Workers
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
1. Create a workspace, install dependencies, save it
|
|
13
|
+
POST /api/v1/mags-jobs (workspace_id: "my-base", script: "apk add python3 nodejs ...")
|
|
14
|
+
|
|
15
|
+
2. Use that workspace as a read-only base for ephemeral workers
|
|
16
|
+
POST /api/v1/mags-jobs (base_workspace_id: "my-base", script: "npm test")
|
|
17
|
+
POST /api/v1/mags-jobs (base_workspace_id: "my-base", script: "python3 run.py")
|
|
18
|
+
...workers run in parallel, base is never modified
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Endpoints
|
|
24
|
+
|
|
25
|
+
### Submit Job
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
POST /api/v1/mags-jobs
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Creates a VM, runs a script, and returns. The VM boots in ~300ms from a pool.
|
|
32
|
+
|
|
33
|
+
**Request:**
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"script": "echo hello world",
|
|
38
|
+
"type": "inline",
|
|
39
|
+
"workspace_id": "my-project",
|
|
40
|
+
"base_workspace_id": "my-base",
|
|
41
|
+
"persistent": false,
|
|
42
|
+
"startup_command": "python3 -m http.server 8080",
|
|
43
|
+
"environment": { "FOO": "bar" },
|
|
44
|
+
"file_ids": ["file-uuid-1", "file-uuid-2"]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Field | Type | Required | Description |
|
|
49
|
+
|-------|------|----------|-------------|
|
|
50
|
+
| `script` | string | yes | Shell script to execute inside the VM |
|
|
51
|
+
| `type` | string | yes | Always `"inline"` |
|
|
52
|
+
| `workspace_id` | string | no | Persistent workspace name. Filesystem changes (apt install, files, configs) are synced to S3 and restored on next run. Omit for truly ephemeral (no sync). |
|
|
53
|
+
| `base_workspace_id` | string | no | Mount an existing workspace **read-only** as the starting filesystem. Changes are discarded unless `workspace_id` is also set (fork pattern). |
|
|
54
|
+
| `persistent` | bool | no | If `true`, VM stays alive after script finishes. Use for long-running servers, SSH access, or URL-exposed services. |
|
|
55
|
+
| `startup_command` | string | no | Command to run when a sleeping persistent VM wakes up (on URL access). |
|
|
56
|
+
| `environment` | object | no | Key-value env vars injected into the VM. |
|
|
57
|
+
| `file_ids` | string[] | no | File IDs from the upload endpoint. Files are downloaded into `/root/` before script runs. |
|
|
58
|
+
|
|
59
|
+
**Response (202):**
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"request_id": "eab1e214-39c8-4ecc-b941-75551976e0fa",
|
|
64
|
+
"status": "accepted",
|
|
65
|
+
"message": "Mags job submitted successfully"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Workspace modes:**
|
|
70
|
+
|
|
71
|
+
| `workspace_id` | `base_workspace_id` | Behavior |
|
|
72
|
+
|-----------------|----------------------|----------|
|
|
73
|
+
| omit | omit | Fully ephemeral. No S3 sync. Fastest. |
|
|
74
|
+
| `"my-ws"` | omit | Read+write workspace. Changes persist across runs. |
|
|
75
|
+
| omit | `"my-base"` | Base mounted read-only. Changes discarded after run. |
|
|
76
|
+
| `"fork-1"` | `"my-base"` | Fork: starts from base, saves changes to `fork-1`. |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### Get Job Status
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
GET /api/v1/mags-jobs/:id/status
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Response (200):**
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"request_id": "eab1e214-...",
|
|
91
|
+
"status": "running",
|
|
92
|
+
"vm_id": "a488ceef",
|
|
93
|
+
"persistent": true,
|
|
94
|
+
"subdomain": "28dfed70c32e",
|
|
95
|
+
"url": "https://28dfed70c32e.apps.magpiecloud.com",
|
|
96
|
+
"sleeping": false,
|
|
97
|
+
"exit_code": 0,
|
|
98
|
+
"queue_duration_ms": 2,
|
|
99
|
+
"acquire_duration_ms": 45,
|
|
100
|
+
"script_duration_ms": 1200,
|
|
101
|
+
"started_at": "2026-02-04T22:30:00Z",
|
|
102
|
+
"completed_at": null
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Status values:** `pending` → `running` → `completed` | `error` | `sleeping`
|
|
107
|
+
|
|
108
|
+
Poll this endpoint to wait for job completion. For persistent jobs, `running` is the final state (VM stays alive).
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### Get Job Logs
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
GET /api/v1/mags-jobs/:id/logs
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Response (200):**
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"logs": [
|
|
123
|
+
{ "timestamp": "2026-02-04T22:30:01Z", "level": "info", "message": "hello world", "source": "stdout" },
|
|
124
|
+
{ "timestamp": "2026-02-04T22:30:01Z", "level": "error", "message": "warning: ...", "source": "stderr" }
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Filter by `source`: `"stdout"`, `"stderr"`, or `"system"` (internal events).
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### List Jobs
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
GET /api/v1/mags-jobs?page=1&page_size=20
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Response (200):**
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"jobs": [
|
|
144
|
+
{
|
|
145
|
+
"request_id": "eab1e214-...",
|
|
146
|
+
"job_id": "372525f4-...",
|
|
147
|
+
"name": "mags-job-1738703400",
|
|
148
|
+
"status": "completed",
|
|
149
|
+
"workspace_id": "my-project",
|
|
150
|
+
"exit_code": 0,
|
|
151
|
+
"created_at": "2026-02-04T22:30:00Z"
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"total": 42,
|
|
155
|
+
"page": 1,
|
|
156
|
+
"page_size": 20
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### Enable Access (SSH or HTTP)
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
POST /api/v1/mags-jobs/:id/access
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Enables external access to a running VM. Use `port: 22` for SSH, any other port for HTTP proxy.
|
|
169
|
+
|
|
170
|
+
**Request:**
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{ "port": 22 }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Response (200) — SSH (port 22):**
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"success": true,
|
|
181
|
+
"message": "SSH proxy enabled",
|
|
182
|
+
"ssh_host": "api.magpiecloud.com",
|
|
183
|
+
"ssh_port": 40000,
|
|
184
|
+
"ssh_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n",
|
|
185
|
+
"access_type": "ssh_proxy"
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Connect with: `ssh -i <key_file> -p <ssh_port> root@<ssh_host>`
|
|
190
|
+
|
|
191
|
+
**Response (200) — HTTP (other ports):**
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"success": true,
|
|
196
|
+
"message": "External access enabled",
|
|
197
|
+
"ipv6_address": "2a01:4f9:...",
|
|
198
|
+
"port": 8080,
|
|
199
|
+
"access_type": "http_proxy"
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
HTTP services are also available via subdomain: `https://<subdomain>.apps.magpiecloud.com`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Update Job
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
PATCH /api/v1/mags-jobs/:id
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Update a job's startup command (used when waking from sleep).
|
|
214
|
+
|
|
215
|
+
**Request:**
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{ "startup_command": "python3 -m http.server 8080" }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Response (200):**
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{ "success": true, "message": "Job updated" }
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### Upload File
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
POST /api/v1/mags-files
|
|
233
|
+
Content-Type: multipart/form-data
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Upload a file (max 100MB) that can be attached to jobs via `file_ids`.
|
|
237
|
+
|
|
238
|
+
**Request:** Multipart form with field name `file`.
|
|
239
|
+
|
|
240
|
+
**Response (201):**
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"file_id": "a1b2c3d4-...",
|
|
245
|
+
"file_name": "script.py",
|
|
246
|
+
"size": 4096
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Examples
|
|
253
|
+
|
|
254
|
+
### 1. Set up a base workspace with dependencies
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
|
|
258
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
259
|
+
-H "Content-Type: application/json" \
|
|
260
|
+
-d '{
|
|
261
|
+
"script": "apk add python3 py3-pip nodejs npm && pip install requests flask",
|
|
262
|
+
"type": "inline",
|
|
263
|
+
"workspace_id": "python-base"
|
|
264
|
+
}'
|
|
265
|
+
# Returns request_id — poll /status until completed
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 2. Spawn ephemeral workers from the base
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Worker 1 — changes discarded, base untouched
|
|
272
|
+
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
|
|
273
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
274
|
+
-H "Content-Type: application/json" \
|
|
275
|
+
-d '{
|
|
276
|
+
"script": "python3 -c \"import requests; print(requests.get('https://httpbin.org/ip').text)\"",
|
|
277
|
+
"type": "inline",
|
|
278
|
+
"base_workspace_id": "python-base"
|
|
279
|
+
}'
|
|
280
|
+
|
|
281
|
+
# Worker 2 — runs in parallel, isolated from worker 1
|
|
282
|
+
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
|
|
283
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
284
|
+
-H "Content-Type: application/json" \
|
|
285
|
+
-d '{
|
|
286
|
+
"script": "node -e \"console.log(process.version)\"",
|
|
287
|
+
"type": "inline",
|
|
288
|
+
"base_workspace_id": "python-base"
|
|
289
|
+
}'
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 3. Fork a base into a new workspace
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
|
|
296
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
297
|
+
-H "Content-Type: application/json" \
|
|
298
|
+
-d '{
|
|
299
|
+
"script": "pip install pandas && python3 -c \"import pandas; print(pandas.__version__)\"",
|
|
300
|
+
"type": "inline",
|
|
301
|
+
"base_workspace_id": "python-base",
|
|
302
|
+
"workspace_id": "python-data"
|
|
303
|
+
}'
|
|
304
|
+
# python-data now has everything from python-base + pandas
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 4. Run a persistent server with URL
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
|
|
311
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
312
|
+
-H "Content-Type: application/json" \
|
|
313
|
+
-d '{
|
|
314
|
+
"script": "echo \"<h1>Hello</h1>\" > index.html && python3 -m http.server 8080",
|
|
315
|
+
"type": "inline",
|
|
316
|
+
"workspace_id": "my-server",
|
|
317
|
+
"persistent": true,
|
|
318
|
+
"startup_command": "python3 -m http.server 8080"
|
|
319
|
+
}'
|
|
320
|
+
# After status=running, enable URL access:
|
|
321
|
+
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs/$REQUEST_ID/access \
|
|
322
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
323
|
+
-H "Content-Type: application/json" \
|
|
324
|
+
-d '{"port": 8080}'
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 5. Execute a command on a running VM via SSH
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
# Enable SSH proxy
|
|
331
|
+
RESP=$(curl -s -X POST https://api.magpiecloud.com/api/v1/mags-jobs/$REQUEST_ID/access \
|
|
332
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
333
|
+
-H "Content-Type: application/json" \
|
|
334
|
+
-d '{"port": 22}')
|
|
335
|
+
|
|
336
|
+
# Extract connection info
|
|
337
|
+
SSH_HOST=$(echo $RESP | jq -r '.ssh_host')
|
|
338
|
+
SSH_PORT=$(echo $RESP | jq -r '.ssh_port')
|
|
339
|
+
echo "$RESP" | jq -r '.ssh_private_key' > /tmp/mags_key && chmod 600 /tmp/mags_key
|
|
340
|
+
|
|
341
|
+
# Connect
|
|
342
|
+
ssh -i /tmp/mags_key -p $SSH_PORT -o StrictHostKeyChecking=no root@$SSH_HOST "echo hello"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Error Responses
|
|
348
|
+
|
|
349
|
+
All errors return:
|
|
350
|
+
|
|
351
|
+
```json
|
|
352
|
+
{ "error": "description of what went wrong" }
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
| Status | Meaning |
|
|
356
|
+
|--------|---------|
|
|
357
|
+
| 400 | Invalid request (missing fields, bad JSON) |
|
|
358
|
+
| 401 | Missing or invalid auth token |
|
|
359
|
+
| 404 | Job not found |
|
|
360
|
+
| 500 | Server error (includes workspace conflict messages) |
|
|
361
|
+
|
|
362
|
+
**Workspace conflict (500):**
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"error": "workspace 'my-ws' is already in use by job eab1e214-... (status: running); use 'mags exec my-ws <command>' to run commands on it, or 'mags ssh my-ws' to connect interactively"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## VM Environment
|
|
373
|
+
|
|
374
|
+
- **OS:** Alpine Linux (lightweight, ~50MB rootfs)
|
|
375
|
+
- **Shell:** `/bin/sh` (ash)
|
|
376
|
+
- **Package manager:** `apk add <package>`
|
|
377
|
+
- **User:** `root`
|
|
378
|
+
- **Working directory:** `/root`
|
|
379
|
+
- **Filesystem:** OverlayFS on top of base rootfs. Workspace changes sync to S3.
|
|
380
|
+
- **Boot time:** ~300ms from pool
|
|
381
|
+
- **Default timeout:** 300 seconds (configurable per job)
|