@magpiecloud/mags 1.8.16 → 1.8.17
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 +388 -0
- package/Mags-API.postman_collection.json +374 -0
- package/QUICKSTART.md +295 -0
- package/README.md +378 -95
- package/bin/mags.js +23 -2
- package/deploy-page.sh +171 -0
- package/index.js +0 -2
- package/mags +0 -0
- package/mags.sh +270 -0
- package/nodejs/README.md +197 -0
- package/nodejs/bin/mags.js +1882 -0
- package/nodejs/index.js +603 -0
- package/nodejs/package.json +45 -0
- package/package.json +3 -18
- package/python/INTEGRATION.md +800 -0
- package/python/README.md +161 -0
- package/python/dist/magpie_mags-1.3.8-py3-none-any.whl +0 -0
- package/python/dist/magpie_mags-1.3.8.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 +186 -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 +527 -0
- package/python/test_sdk.py +78 -0
- package/skill.md +153 -0
- package/website/api.html +1095 -0
- package/website/claude-skill.html +481 -0
- package/website/cookbook/hn-marketing.html +410 -0
- package/website/cookbook/hn-marketing.sh +42 -0
- package/website/cookbook.html +282 -0
- package/website/docs.html +677 -0
- package/website/env.js +4 -0
- package/website/index.html +801 -0
- package/website/llms.txt +334 -0
- package/website/login.html +108 -0
- package/website/mags.md +210 -0
- package/website/script.js +453 -0
- package/website/styles.css +1075 -0
- package/website/tokens.html +169 -0
- package/website/usage.html +185 -0
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mags - Instant VM Execution
|
|
2
2
|
|
|
3
|
-
Execute scripts instantly on Magpie's microVM infrastructure. VMs boot in <100ms from a warm pool.
|
|
3
|
+
Execute scripts instantly on Magpie's microVM infrastructure. VMs boot in <100ms from a warm pool, giving you instant access to isolated compute environments.
|
|
4
|
+
|
|
5
|
+
## What is Mags?
|
|
6
|
+
|
|
7
|
+
Mags is a CLI and SDK for running scripts on ephemeral microVMs. Each execution gets its own isolated VM that:
|
|
8
|
+
|
|
9
|
+
- Boots in ~300ms (from warm pool)
|
|
10
|
+
- Supports optional S3-backed persistent workspaces (with `-p` flag)
|
|
11
|
+
- Syncs /root directory automatically when persistence is enabled
|
|
12
|
+
- Can expose public URLs for web services
|
|
13
|
+
- Provides SSH access through secure proxy
|
|
14
|
+
- Auto-sleeps when idle and wakes on request
|
|
4
15
|
|
|
5
16
|
## Installation
|
|
6
17
|
|
|
@@ -8,176 +19,411 @@ Execute scripts instantly on Magpie's microVM infrastructure. VMs boot in <100ms
|
|
|
8
19
|
npm install -g @magpiecloud/mags
|
|
9
20
|
```
|
|
10
21
|
|
|
11
|
-
##
|
|
22
|
+
## Authentication
|
|
12
23
|
|
|
13
|
-
###
|
|
24
|
+
### Interactive Login (Recommended)
|
|
14
25
|
|
|
15
26
|
```bash
|
|
16
27
|
mags login
|
|
17
28
|
```
|
|
18
29
|
|
|
19
|
-
This
|
|
30
|
+
This opens your browser to the Magpie dashboard where you can create an API token. Paste the token when prompted, and it will be saved to `~/.mags/config.json` for all future commands.
|
|
20
31
|
|
|
21
|
-
###
|
|
32
|
+
### Auth Commands
|
|
22
33
|
|
|
23
34
|
```bash
|
|
24
|
-
mags
|
|
25
|
-
mags
|
|
26
|
-
mags
|
|
35
|
+
mags login # Authenticate with Magpie
|
|
36
|
+
mags whoami # Check current authentication status
|
|
37
|
+
mags logout # Remove saved credentials
|
|
27
38
|
```
|
|
28
39
|
|
|
29
|
-
###
|
|
40
|
+
### Environment Variable (Alternative)
|
|
30
41
|
|
|
31
42
|
```bash
|
|
32
|
-
|
|
43
|
+
export MAGS_API_TOKEN="your-token-here"
|
|
33
44
|
```
|
|
34
45
|
|
|
35
|
-
##
|
|
46
|
+
## Quick Start
|
|
36
47
|
|
|
37
|
-
###
|
|
48
|
+
### Run a simple command
|
|
38
49
|
|
|
39
50
|
```bash
|
|
40
|
-
mags
|
|
51
|
+
mags run 'echo Hello World'
|
|
41
52
|
```
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
### Other Auth Commands
|
|
54
|
+
### Create a sandbox
|
|
46
55
|
|
|
47
56
|
```bash
|
|
48
|
-
|
|
49
|
-
mags
|
|
57
|
+
# Create a new VM (local disk only)
|
|
58
|
+
mags new myproject
|
|
59
|
+
|
|
60
|
+
# Create with S3 data persistence
|
|
61
|
+
mags new myproject -p
|
|
62
|
+
|
|
63
|
+
# SSH into it
|
|
64
|
+
mags ssh myproject
|
|
50
65
|
```
|
|
51
66
|
|
|
52
|
-
###
|
|
67
|
+
### Run with a persistent workspace
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
Workspaces persist data between runs using S3 sync:
|
|
55
70
|
|
|
56
71
|
```bash
|
|
57
|
-
|
|
72
|
+
# First run - install dependencies
|
|
73
|
+
mags run -w myproject 'apk add python3 py3-pip && pip install requests'
|
|
74
|
+
|
|
75
|
+
# Second run - dependencies are still there
|
|
76
|
+
mags run -w myproject 'python3 -c "import requests; print(requests.__version__)"'
|
|
58
77
|
```
|
|
59
78
|
|
|
60
|
-
|
|
79
|
+
### Deploy a web service with public URL
|
|
61
80
|
|
|
62
81
|
```bash
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
mags run -p --url 'python3 -m http.server 8080'
|
|
83
|
+
# Returns: https://abc123.apps.magpiecloud.com
|
|
84
|
+
```
|
|
65
85
|
|
|
66
|
-
|
|
67
|
-
mags new myproject -p
|
|
86
|
+
## CLI Reference
|
|
68
87
|
|
|
69
|
-
|
|
70
|
-
mags ssh myproject
|
|
88
|
+
### Commands
|
|
71
89
|
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
| Command | Description |
|
|
91
|
+
|---------|-------------|
|
|
92
|
+
| `mags login` | Authenticate with Magpie (saves token locally) |
|
|
93
|
+
| `mags logout` | Remove saved credentials |
|
|
94
|
+
| `mags whoami` | Show current authentication status |
|
|
95
|
+
| `mags new <workspace> [-p]` | Create a VM sandbox (add `-p` for S3 persistence) |
|
|
96
|
+
| `mags run [options] <script>` | Execute a script on a microVM |
|
|
97
|
+
| `mags ssh <workspace>` | Open interactive SSH session to a VM |
|
|
98
|
+
| `mags status <job-id>` | Get job status |
|
|
99
|
+
| `mags logs <job-id>` | Get job logs |
|
|
100
|
+
| `mags list` | List recent jobs |
|
|
101
|
+
| `mags url <job-id> [port]` | Enable URL access for a job |
|
|
102
|
+
| `mags stop <job-id>` | Stop a running job |
|
|
103
|
+
| `mags set <name\|id> [options]` | Update VM settings (`--no-sleep`, `--sleep`) |
|
|
74
104
|
|
|
75
|
-
|
|
76
|
-
mags run -w my-project 'apk add nodejs && node --version'
|
|
105
|
+
### Run Options
|
|
77
106
|
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
| Flag | Description | Default |
|
|
108
|
+
|------|-------------|---------|
|
|
109
|
+
| `-w, --workspace <id>` | Use persistent workspace (S3 sync) | auto-generated |
|
|
110
|
+
| `-p, --persistent` | Keep VM alive after script completes | false |
|
|
111
|
+
| `--url` | Enable public URL access (requires -p) | false |
|
|
112
|
+
| `--port <port>` | Port to expose for URL access | 8080 |
|
|
113
|
+
| `--no-sleep` | Keep VM always running, never auto-sleep (requires -p) | false |
|
|
114
|
+
| `--startup-command <cmd>` | Command to run when VM wakes from sleep | none |
|
|
115
|
+
|
|
116
|
+
## SSH Access
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
mags run -w webapp -p --url --startup-command 'npm start' 'npm install && npm start'
|
|
118
|
+
Connect to any running VM via SSH:
|
|
83
119
|
|
|
84
|
-
|
|
85
|
-
mags
|
|
120
|
+
```bash
|
|
121
|
+
mags ssh myproject
|
|
122
|
+
```
|
|
86
123
|
|
|
87
|
-
|
|
88
|
-
mags url <job-id>
|
|
89
|
-
mags url <job-id> 8080
|
|
124
|
+
### SSH Features
|
|
90
125
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
mags
|
|
95
|
-
mags stop <job-id>
|
|
126
|
+
- **Secure proxy**: Connect via `api.magpiecloud.com:PORT` (agent IPs are hidden)
|
|
127
|
+
- **PTY support**: Full interactive terminal with colors and editors
|
|
128
|
+
- **Key-based auth**: Automatic SSH key management
|
|
129
|
+
- **Hostname**: VMs use `mags-vm` as hostname
|
|
96
130
|
|
|
97
|
-
|
|
98
|
-
|
|
131
|
+
### SSH Session Example
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
$ mags ssh myproject
|
|
135
|
+
Connecting to api.magpiecloud.com:40006...
|
|
136
|
+
(Use Ctrl+D or 'exit' to disconnect)
|
|
137
|
+
|
|
138
|
+
mags-vm:~# ls
|
|
139
|
+
index.html test.txt
|
|
140
|
+
|
|
141
|
+
mags-vm:~# python3 --version
|
|
142
|
+
Python 3.11.6
|
|
143
|
+
|
|
144
|
+
mags-vm:~# exit
|
|
99
145
|
```
|
|
100
146
|
|
|
101
|
-
##
|
|
147
|
+
## Workspaces & Persistence
|
|
148
|
+
|
|
149
|
+
### How It Works
|
|
150
|
+
|
|
151
|
+
When you use a workspace, Mags:
|
|
102
152
|
|
|
103
|
-
|
|
153
|
+
1. Mounts a JuiceFS filesystem backed by S3
|
|
154
|
+
2. Creates an OverlayFS to capture all filesystem changes
|
|
155
|
+
3. Syncs your `/root` directory to S3 every 30 seconds
|
|
156
|
+
4. Restores your files when you reconnect
|
|
157
|
+
|
|
158
|
+
### What Gets Persisted
|
|
159
|
+
|
|
160
|
+
- `/root` - Your home directory (synced every 30 seconds)
|
|
161
|
+
- `/jfs` - Direct JuiceFS mount (instant S3 sync)
|
|
162
|
+
- Installed packages and dependencies
|
|
163
|
+
- Configuration files and dotfiles
|
|
164
|
+
|
|
165
|
+
### Sync Behavior
|
|
166
|
+
|
|
167
|
+
| Event | Behavior |
|
|
168
|
+
|-------|----------|
|
|
169
|
+
| While running | Auto-sync every 30 seconds |
|
|
170
|
+
| On stop | Full sync before VM terminates |
|
|
171
|
+
| On wake | Previous state restored instantly |
|
|
172
|
+
|
|
173
|
+
## Usage Examples
|
|
174
|
+
|
|
175
|
+
### Basic Execution
|
|
104
176
|
|
|
105
177
|
```bash
|
|
106
|
-
|
|
178
|
+
# Simple command
|
|
179
|
+
mags run 'echo Hello World'
|
|
180
|
+
|
|
181
|
+
# Multi-line script
|
|
182
|
+
mags run 'apk add curl && curl -s https://api.github.com/users/octocat | head -5'
|
|
183
|
+
|
|
184
|
+
# With environment info
|
|
185
|
+
mags run 'uname -a && cat /etc/os-release'
|
|
107
186
|
```
|
|
108
187
|
|
|
109
|
-
|
|
110
|
-
- `/mags run echo Hello World`
|
|
111
|
-
- `/mags create a python environment with numpy and pandas`
|
|
112
|
-
- `/mags run a flask server and give me the public URL`
|
|
188
|
+
### Persistent Workspaces
|
|
113
189
|
|
|
114
|
-
|
|
190
|
+
```bash
|
|
191
|
+
# Create a workspace with dependencies
|
|
192
|
+
mags run -w ml-project 'apk add python3 py3-pip && pip install numpy pandas scikit-learn'
|
|
115
193
|
|
|
116
|
-
|
|
194
|
+
# Run scripts using the workspace
|
|
195
|
+
mags run -w ml-project 'python3 train.py'
|
|
117
196
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
| `-p, --persistent` | Keep VM alive for URL/SSH access | false |
|
|
122
|
-
| `--url` | Enable public URL access (requires -p) | false |
|
|
123
|
-
| `--port` | Port to expose for URL access | 8080 |
|
|
124
|
-
| `--startup-command` | Command when VM wakes from sleep | none |
|
|
197
|
+
# All files in /root are persisted
|
|
198
|
+
mags run -w ml-project 'ls -la /root'
|
|
199
|
+
```
|
|
125
200
|
|
|
126
|
-
|
|
201
|
+
### Web Services
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Simple static server
|
|
205
|
+
mags run -p --url 'python3 -m http.server 8080'
|
|
206
|
+
|
|
207
|
+
# Node.js app
|
|
208
|
+
mags run -w myapp -p --url --port 3000 'npm install && npm start'
|
|
209
|
+
|
|
210
|
+
# Flask app
|
|
211
|
+
mags run -w flask-app -p --url 'pip install flask && python app.py'
|
|
212
|
+
|
|
213
|
+
# Custom startup command for auto-wake
|
|
214
|
+
mags run -w api -p --url --startup-command 'python server.py' 'pip install -r requirements.txt && python server.py'
|
|
215
|
+
```
|
|
127
216
|
|
|
128
|
-
|
|
217
|
+
### Always-On VMs
|
|
218
|
+
|
|
219
|
+
By default, persistent VMs auto-sleep after 10 minutes of inactivity and wake on the next request. Use `--no-sleep` to keep a VM running 24/7:
|
|
129
220
|
|
|
130
221
|
```bash
|
|
131
|
-
|
|
222
|
+
# Always-on API server (never auto-sleeps)
|
|
223
|
+
mags run -w my-api -p --no-sleep --url --port 3000 'npm start'
|
|
224
|
+
|
|
225
|
+
# Always-on background worker
|
|
226
|
+
mags run -w worker -p --no-sleep 'python worker.py'
|
|
132
227
|
```
|
|
133
228
|
|
|
134
|
-
|
|
135
|
-
- Connects via secure proxy (`api.magpiecloud.com:PORT`)
|
|
136
|
-
- Full PTY support for interactive terminals
|
|
137
|
-
- Automatic SSH key management
|
|
138
|
-
- Hostname: `mags-vm`
|
|
229
|
+
If an always-on VM's host becomes unhealthy, the orchestrator automatically re-provisions it on a healthy agent within ~60 seconds.
|
|
139
230
|
|
|
140
|
-
|
|
231
|
+
### Interactive Development
|
|
141
232
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
233
|
+
```bash
|
|
234
|
+
# Create a dev environment (local disk)
|
|
235
|
+
mags new dev-env
|
|
236
|
+
|
|
237
|
+
# SSH in and work
|
|
238
|
+
mags ssh dev-env
|
|
146
239
|
|
|
147
|
-
|
|
240
|
+
# Inside the VM:
|
|
241
|
+
mags-vm:~# apk add git nodejs npm
|
|
242
|
+
mags-vm:~# git clone https://github.com/user/repo
|
|
243
|
+
mags-vm:~# cd repo && npm install
|
|
244
|
+
mags-vm:~# npm run dev
|
|
245
|
+
```
|
|
148
246
|
|
|
149
247
|
## Node.js SDK
|
|
150
248
|
|
|
249
|
+
For programmatic access, use the SDK:
|
|
250
|
+
|
|
151
251
|
```javascript
|
|
152
252
|
const Mags = require('@magpiecloud/mags');
|
|
153
253
|
|
|
154
254
|
const mags = new Mags({
|
|
155
255
|
apiToken: process.env.MAGS_API_TOKEN
|
|
156
256
|
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### SDK Methods
|
|
260
|
+
|
|
261
|
+
#### Jobs
|
|
262
|
+
|
|
263
|
+
| Method | Description |
|
|
264
|
+
|--------|-------------|
|
|
265
|
+
| `run(script, options)` | Submit a job. Options: `name`, `workspaceId`, `baseWorkspaceId`, `persistent`, `noSleep`, `ephemeral`, `startupCommand`, `environment`, `fileIds`, `diskGb` |
|
|
266
|
+
| `runAndWait(script, options)` | Submit and block until done. Extra options: `timeout`, `pollInterval` |
|
|
267
|
+
| `new(name, options)` | Create a VM sandbox and wait until running. Options: `persistent`, `baseWorkspaceId`, `diskGb`, `timeout` |
|
|
268
|
+
| `status(requestId)` | Get job status |
|
|
269
|
+
| `logs(requestId)` | Get job logs |
|
|
270
|
+
| `list({page, pageSize})` | List recent jobs (paginated) |
|
|
271
|
+
| `findJob(nameOrId)` | Find job by name, workspace ID, or request ID |
|
|
272
|
+
| `updateJob(requestId, {startupCommand, noSleep})` | Update job settings |
|
|
273
|
+
| `stop(nameOrId)` | Stop a job (accepts name, workspace ID, or request ID) |
|
|
274
|
+
| `exec(nameOrId, command, {timeout})` | Execute a command on a running/sleeping VM via SSH |
|
|
275
|
+
| `sync(requestId)` | Sync workspace to S3 without stopping |
|
|
276
|
+
| `resize(workspace, diskGb)` | Resize workspace disk (stops VM, creates new one) |
|
|
277
|
+
| `usage({windowDays})` | Get aggregated usage summary |
|
|
278
|
+
|
|
279
|
+
#### URL & Access
|
|
280
|
+
|
|
281
|
+
| Method | Description |
|
|
282
|
+
|--------|-------------|
|
|
283
|
+
| `enableAccess(requestId, port)` | Enable SSH (port 22) or HTTP access (default 8080) |
|
|
284
|
+
| `url(nameOrId, port)` | Enable public URL and return the full URL |
|
|
285
|
+
| `urlAliasCreate(subdomain, workspaceId, domain)` | Create a stable URL alias for a workspace |
|
|
286
|
+
| `urlAliasList()` | List all URL aliases |
|
|
287
|
+
| `urlAliasDelete(subdomain)` | Delete a URL alias |
|
|
288
|
+
|
|
289
|
+
#### Workspaces
|
|
290
|
+
|
|
291
|
+
| Method | Description |
|
|
292
|
+
|--------|-------------|
|
|
293
|
+
| `listWorkspaces()` | List all workspaces |
|
|
294
|
+
| `deleteWorkspace(workspaceId)` | Delete a workspace and its S3 data |
|
|
295
|
+
|
|
296
|
+
#### Files
|
|
297
|
+
|
|
298
|
+
| Method | Description |
|
|
299
|
+
|--------|-------------|
|
|
300
|
+
| `uploadFiles(filePaths)` | Upload local files, returns array of file IDs |
|
|
301
|
+
|
|
302
|
+
#### Cron Jobs
|
|
303
|
+
|
|
304
|
+
| Method | Description |
|
|
305
|
+
|--------|-------------|
|
|
306
|
+
| `cronCreate({name, cronExpression, script, workspaceId, environment, persistent})` | Create a scheduled job |
|
|
307
|
+
| `cronList()` | List all cron jobs |
|
|
308
|
+
| `cronGet(id)` | Get a cron job |
|
|
309
|
+
| `cronUpdate(id, updates)` | Update a cron job |
|
|
310
|
+
| `cronDelete(id)` | Delete a cron job |
|
|
311
|
+
|
|
312
|
+
### SDK Examples
|
|
157
313
|
|
|
314
|
+
```javascript
|
|
158
315
|
// Run and wait for completion
|
|
159
316
|
const result = await mags.runAndWait('echo Hello World');
|
|
160
317
|
console.log(result.logs);
|
|
161
318
|
|
|
162
|
-
//
|
|
163
|
-
|
|
319
|
+
// Create a sandbox (local disk)
|
|
320
|
+
await mags.new('myproject');
|
|
321
|
+
|
|
322
|
+
// Create a sandbox with S3 persistence
|
|
323
|
+
await mags.new('myproject', { persistent: true });
|
|
324
|
+
|
|
325
|
+
// Execute command on existing VM
|
|
326
|
+
const { output } = await mags.exec('myproject', 'ls -la /root');
|
|
327
|
+
console.log(output);
|
|
328
|
+
|
|
329
|
+
// Run with workspace and options
|
|
330
|
+
const job = await mags.run('python script.py', {
|
|
164
331
|
workspaceId: 'myproject',
|
|
165
|
-
persistent: true
|
|
332
|
+
persistent: true,
|
|
333
|
+
diskGb: 5,
|
|
334
|
+
startupCommand: 'python server.py'
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Always-on VM (never auto-sleeps)
|
|
338
|
+
await mags.run('node server.js', {
|
|
339
|
+
workspaceId: 'my-api',
|
|
340
|
+
persistent: true,
|
|
341
|
+
noSleep: true
|
|
166
342
|
});
|
|
167
343
|
|
|
168
|
-
//
|
|
169
|
-
const
|
|
344
|
+
// Enable public URL
|
|
345
|
+
const { url } = await mags.url('my-api', 3000);
|
|
346
|
+
console.log(url); // https://abc123.apps.magpiecloud.com
|
|
347
|
+
|
|
348
|
+
// Create stable URL alias
|
|
349
|
+
await mags.urlAliasCreate('my-app', 'my-api');
|
|
350
|
+
|
|
351
|
+
// Resize workspace disk
|
|
352
|
+
await mags.resize('myproject', 10); // 10GB
|
|
353
|
+
|
|
354
|
+
// Stop a job by name
|
|
355
|
+
await mags.stop('myproject');
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## API Endpoints
|
|
359
|
+
|
|
360
|
+
For direct API access:
|
|
170
361
|
|
|
171
|
-
|
|
172
|
-
|
|
362
|
+
| Endpoint | Method | Description |
|
|
363
|
+
|----------|--------|-------------|
|
|
364
|
+
| `/api/v1/mags-jobs` | POST | Submit a new job |
|
|
365
|
+
| `/api/v1/mags-jobs` | GET | List jobs (paginated) |
|
|
366
|
+
| `/api/v1/mags-jobs/{id}/status` | GET | Get job status |
|
|
367
|
+
| `/api/v1/mags-jobs/{id}/logs` | GET | Get job logs |
|
|
368
|
+
| `/api/v1/mags-jobs/{id}/access` | POST | Enable SSH/URL access |
|
|
369
|
+
| `/api/v1/mags-jobs/{id}` | PATCH | Update job settings |
|
|
173
370
|
|
|
174
|
-
|
|
175
|
-
const jobs = await mags.list({ page: 1, pageSize: 10 });
|
|
371
|
+
### Example API Request
|
|
176
372
|
|
|
177
|
-
|
|
178
|
-
|
|
373
|
+
```bash
|
|
374
|
+
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
|
|
375
|
+
-H "Authorization: Bearer $MAGS_API_TOKEN" \
|
|
376
|
+
-H "Content-Type: application/json" \
|
|
377
|
+
-d '{
|
|
378
|
+
"script": "echo Hello World",
|
|
379
|
+
"type": "inline",
|
|
380
|
+
"workspace_id": "myproject",
|
|
381
|
+
"persistent": true,
|
|
382
|
+
"no_sleep": true
|
|
383
|
+
}'
|
|
179
384
|
```
|
|
180
385
|
|
|
386
|
+
## Job Status Values
|
|
387
|
+
|
|
388
|
+
| Status | Description |
|
|
389
|
+
|--------|-------------|
|
|
390
|
+
| `pending` | Job queued, waiting for VM |
|
|
391
|
+
| `running` | Script executing |
|
|
392
|
+
| `sleeping` | Persistent VM idle (wakes on request) |
|
|
393
|
+
| `completed` | Script finished successfully |
|
|
394
|
+
| `error` | Script failed |
|
|
395
|
+
|
|
396
|
+
## VM Environment
|
|
397
|
+
|
|
398
|
+
Each VM runs Alpine Linux with hostname `mags-vm` and includes:
|
|
399
|
+
|
|
400
|
+
- `/root` - Persistent home directory (synced to S3)
|
|
401
|
+
- `/jfs` - Direct JuiceFS mount
|
|
402
|
+
- Common tools: curl, wget, git, python3, nodejs
|
|
403
|
+
- Package manager: `apk add <package>`
|
|
404
|
+
|
|
405
|
+
### Installing Packages
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Python packages
|
|
409
|
+
mags run 'apk add python3 py3-pip && pip install requests flask pandas'
|
|
410
|
+
|
|
411
|
+
# Node.js packages
|
|
412
|
+
mags run 'apk add nodejs npm && npm install -g express'
|
|
413
|
+
|
|
414
|
+
# System packages
|
|
415
|
+
mags run 'apk add ffmpeg imagemagick'
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Performance
|
|
419
|
+
|
|
420
|
+
| Metric | Value |
|
|
421
|
+
|--------|-------|
|
|
422
|
+
| Warm start | ~300ms |
|
|
423
|
+
| Cold start | ~4 seconds |
|
|
424
|
+
| Script overhead | ~50ms |
|
|
425
|
+
| Workspace sync | Every 30 seconds |
|
|
426
|
+
|
|
181
427
|
## Environment Variables
|
|
182
428
|
|
|
183
429
|
| Variable | Description | Default |
|
|
@@ -185,12 +431,49 @@ await mags.stop(requestId);
|
|
|
185
431
|
| `MAGS_API_TOKEN` | Your API token (required) | - |
|
|
186
432
|
| `MAGS_API_URL` | API endpoint | https://api.magpiecloud.com |
|
|
187
433
|
|
|
188
|
-
##
|
|
434
|
+
## Troubleshooting
|
|
435
|
+
|
|
436
|
+
### "API token required"
|
|
437
|
+
|
|
438
|
+
Set your API token:
|
|
439
|
+
```bash
|
|
440
|
+
mags login
|
|
441
|
+
# or
|
|
442
|
+
export MAGS_API_TOKEN="your-token-here"
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### "Job timed out"
|
|
446
|
+
|
|
447
|
+
Increase the timeout or check if your script is hanging:
|
|
448
|
+
```bash
|
|
449
|
+
mags run --timeout 600 'long-running-script.sh'
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### "Workspace not found"
|
|
453
|
+
|
|
454
|
+
Workspace IDs are case-sensitive. Check with:
|
|
455
|
+
```bash
|
|
456
|
+
mags list
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### URL not accessible
|
|
460
|
+
|
|
461
|
+
Make sure you're using both `-p` (persistent) and `--url` flags, and your app is listening on the correct port:
|
|
462
|
+
```bash
|
|
463
|
+
mags run -p --url --port 3000 'node server.js'
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### SSH connection issues
|
|
467
|
+
|
|
468
|
+
SSH connects through our proxy. If you have issues:
|
|
469
|
+
1. Ensure the job is running: `mags status <id>`
|
|
470
|
+
2. Try reconnecting: `mags ssh <workspace>`
|
|
471
|
+
|
|
472
|
+
## Support
|
|
189
473
|
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
193
|
-
- **Workspace sync**: Every 30 seconds
|
|
474
|
+
- Documentation: https://mags.run
|
|
475
|
+
- API Reference: https://mags.run/api.html
|
|
476
|
+
- Dashboard: https://mags.run/tokens.html
|
|
194
477
|
|
|
195
478
|
## License
|
|
196
479
|
|
package/bin/mags.js
CHANGED
|
@@ -230,6 +230,7 @@ ${colors.bold}Run Options:${colors.reset}
|
|
|
230
230
|
--url Enable public URL access (requires -p)
|
|
231
231
|
--port <port> Port to expose for URL (default: 8080)
|
|
232
232
|
--startup-command <cmd> Command to run when VM wakes from sleep
|
|
233
|
+
-t, --type <type> VM rootfs type: standard, claude, pi
|
|
233
234
|
|
|
234
235
|
${colors.bold}Cron Commands:${colors.reset}
|
|
235
236
|
cron add [options] <script> Create a scheduled cron job
|
|
@@ -409,6 +410,7 @@ async function newVM(args) {
|
|
|
409
410
|
let baseWorkspace = null;
|
|
410
411
|
let diskGB = 0;
|
|
411
412
|
let persistent = false;
|
|
413
|
+
let rootfsType = '';
|
|
412
414
|
|
|
413
415
|
for (let i = 0; i < args.length; i++) {
|
|
414
416
|
if (args[i] === '-p' || args[i] === '--persistent') {
|
|
@@ -417,6 +419,8 @@ async function newVM(args) {
|
|
|
417
419
|
baseWorkspace = args[++i];
|
|
418
420
|
} else if (args[i] === '--disk' && args[i + 1]) {
|
|
419
421
|
diskGB = parseInt(args[++i]) || 0;
|
|
422
|
+
} else if ((args[i] === '--type' || args[i] === '-t') && args[i + 1]) {
|
|
423
|
+
rootfsType = args[++i];
|
|
420
424
|
} else if (!name) {
|
|
421
425
|
name = args[i];
|
|
422
426
|
}
|
|
@@ -424,8 +428,9 @@ async function newVM(args) {
|
|
|
424
428
|
|
|
425
429
|
if (!name) {
|
|
426
430
|
log('red', 'Error: Name required');
|
|
427
|
-
console.log(`\nUsage: mags new <name> [-p] [--base <workspace>] [--disk <GB>]`);
|
|
428
|
-
console.log(` -p, --persistent Enable S3 data persistence
|
|
431
|
+
console.log(`\nUsage: mags new <name> [-p] [--base <workspace>] [--disk <GB>] [--type <type>]`);
|
|
432
|
+
console.log(` -p, --persistent Enable S3 data persistence`);
|
|
433
|
+
console.log(` -t, --type VM type: standard (default), claude, pi\n`);
|
|
429
434
|
process.exit(1);
|
|
430
435
|
}
|
|
431
436
|
|
|
@@ -457,6 +462,7 @@ async function newVM(args) {
|
|
|
457
462
|
};
|
|
458
463
|
if (baseWorkspace) payload.base_workspace_id = baseWorkspace;
|
|
459
464
|
if (diskGB) payload.disk_gb = diskGB;
|
|
465
|
+
if (rootfsType) payload.rootfs_type = rootfsType;
|
|
460
466
|
|
|
461
467
|
const response = await request('POST', '/api/v1/mags-jobs', payload);
|
|
462
468
|
|
|
@@ -506,6 +512,7 @@ async function runJob(args) {
|
|
|
506
512
|
let startupCommand = '';
|
|
507
513
|
let diskGB = 0;
|
|
508
514
|
let fileArgs = [];
|
|
515
|
+
let rootfsType = '';
|
|
509
516
|
|
|
510
517
|
// Parse flags
|
|
511
518
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -546,6 +553,10 @@ async function runJob(args) {
|
|
|
546
553
|
case '--startup-command':
|
|
547
554
|
startupCommand = args[++i];
|
|
548
555
|
break;
|
|
556
|
+
case '--type':
|
|
557
|
+
case '-t':
|
|
558
|
+
rootfsType = args[++i];
|
|
559
|
+
break;
|
|
549
560
|
default:
|
|
550
561
|
script = args.slice(i).join(' ');
|
|
551
562
|
i = args.length;
|
|
@@ -608,6 +619,7 @@ async function runJob(args) {
|
|
|
608
619
|
if (startupCommand) payload.startup_command = startupCommand;
|
|
609
620
|
if (fileIds.length > 0) payload.file_ids = fileIds;
|
|
610
621
|
if (diskGB) payload.disk_gb = diskGB;
|
|
622
|
+
if (rootfsType) payload.rootfs_type = rootfsType;
|
|
611
623
|
|
|
612
624
|
const response = await request('POST', '/api/v1/mags-jobs', payload);
|
|
613
625
|
|
|
@@ -1508,6 +1520,11 @@ async function proxyTunnel(jobID) {
|
|
|
1508
1520
|
|
|
1509
1521
|
ws.on('open', () => {
|
|
1510
1522
|
// Don't flush yet — wait for first message (SSH key)
|
|
1523
|
+
// Send pings every 30s to keep the connection alive through proxies/CDNs
|
|
1524
|
+
const pingInterval = setInterval(() => {
|
|
1525
|
+
if (ws.readyState === WebSocket.OPEN) ws.ping();
|
|
1526
|
+
}, 30000);
|
|
1527
|
+
ws.on('close', () => clearInterval(pingInterval));
|
|
1511
1528
|
});
|
|
1512
1529
|
|
|
1513
1530
|
ws.on('message', (data, isBinary) => {
|
|
@@ -1672,6 +1689,8 @@ async function sshToJob(nameOrId) {
|
|
|
1672
1689
|
'-o', 'StrictHostKeyChecking=no',
|
|
1673
1690
|
'-o', 'UserKnownHostsFile=/dev/null',
|
|
1674
1691
|
'-o', 'LogLevel=ERROR',
|
|
1692
|
+
'-o', 'ServerAliveInterval=15',
|
|
1693
|
+
'-o', 'ServerAliveCountMax=4',
|
|
1675
1694
|
'-o', `ProxyCommand=${magsPath} proxy ${jobID}`,
|
|
1676
1695
|
'-i', keyFile,
|
|
1677
1696
|
'root@mags-vm',
|
|
@@ -1739,6 +1758,8 @@ async function execOnJob(nameOrId, command) {
|
|
|
1739
1758
|
'-o', 'StrictHostKeyChecking=no',
|
|
1740
1759
|
'-o', 'UserKnownHostsFile=/dev/null',
|
|
1741
1760
|
'-o', 'LogLevel=ERROR',
|
|
1761
|
+
'-o', 'ServerAliveInterval=15',
|
|
1762
|
+
'-o', 'ServerAliveCountMax=4',
|
|
1742
1763
|
'-o', `ProxyCommand=${magsPath} proxy ${jobID}`,
|
|
1743
1764
|
'-i', keyFile,
|
|
1744
1765
|
];
|