@magpiecloud/mags 1.8.4 → 1.8.6
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 +2 -0
- package/QUICKSTART.md +11 -0
- package/README.md +24 -1
- package/bin/mags.js +135 -24
- package/package.json +1 -1
- package/python/README.md +19 -1
- package/python/dist/magpie_mags-1.3.2-py3-none-any.whl +0 -0
- package/python/dist/magpie_mags-1.3.2.tar.gz +0 -0
- package/python/dist/magpie_mags-1.3.3-py3-none-any.whl +0 -0
- package/python/dist/magpie_mags-1.3.3.tar.gz +0 -0
- package/python/pyproject.toml +1 -1
- package/python/src/magpie_mags.egg-info/PKG-INFO +20 -2
- package/python/src/mags/client.py +15 -9
- package/website/api.html +4 -6
- package/website/claude-skill.html +6 -8
- package/website/cookbook/hn-marketing.sh +25 -33
- package/website/cookbook.html +22 -19
- package/website/index.html +50 -17
- package/website/login.html +9 -28
- package/website/tokens.html +4 -7
- package/website/usage.html +4 -7
- package/python/dist/magpie_mags-1.3.0-py3-none-any.whl +0 -0
- package/python/dist/magpie_mags-1.3.0.tar.gz +0 -0
package/API.md
CHANGED
|
@@ -39,6 +39,7 @@ Creates a VM, runs a script, and returns. The VM boots in ~300ms from a pool.
|
|
|
39
39
|
"workspace_id": "my-project",
|
|
40
40
|
"base_workspace_id": "my-base",
|
|
41
41
|
"persistent": false,
|
|
42
|
+
"no_sleep": false,
|
|
42
43
|
"startup_command": "python3 -m http.server 8080",
|
|
43
44
|
"environment": { "FOO": "bar" },
|
|
44
45
|
"file_ids": ["file-uuid-1", "file-uuid-2"]
|
|
@@ -52,6 +53,7 @@ Creates a VM, runs a script, and returns. The VM boots in ~300ms from a pool.
|
|
|
52
53
|
| `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
54
|
| `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
55
|
| `persistent` | bool | no | If `true`, VM stays alive after script finishes. Use for long-running servers, SSH access, or URL-exposed services. |
|
|
56
|
+
| `no_sleep` | bool | no | If `true` with `persistent`, VM never auto-sleeps. Stays running 24/7 and auto-recovers if the host goes down. |
|
|
55
57
|
| `startup_command` | string | no | Command to run when a sleeping persistent VM wakes up (on URL access). |
|
|
56
58
|
| `environment` | object | no | Key-value env vars injected into the VM. |
|
|
57
59
|
| `file_ids` | string[] | no | File IDs from the upload endpoint. Files are downloaded into `/root/` before script runs. |
|
package/QUICKSTART.md
CHANGED
|
@@ -148,6 +148,7 @@ mags run -w webapp-prod "..."
|
|
|
148
148
|
| `-p, --persistent` | Keep VM alive for URL/SSH access | false |
|
|
149
149
|
| `--url` | Enable public URL access (requires -p) | false |
|
|
150
150
|
| `--port` | Port to expose for URL access | 8080 |
|
|
151
|
+
| `--no-sleep` | Keep VM always running, never auto-sleep (requires -p) | false |
|
|
151
152
|
| `--startup-command` | Command when VM wakes from sleep | none |
|
|
152
153
|
| `-e, --ephemeral` | No S3 sync (faster, truly ephemeral) | false |
|
|
153
154
|
| `-t, --timeout` | Timeout in seconds | 300 |
|
|
@@ -205,6 +206,16 @@ mags run -w myapp -p --url --startup-command "npm start" "npm install && npm sta
|
|
|
205
206
|
# The app wakes automatically when someone visits the URL!
|
|
206
207
|
```
|
|
207
208
|
|
|
209
|
+
### Always-On Server (Never Sleeps)
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Deploy a server that stays running 24/7
|
|
213
|
+
mags run -w my-api -p --no-sleep --url --port 3000 "npm start"
|
|
214
|
+
|
|
215
|
+
# VM never auto-sleeps, even when idle
|
|
216
|
+
# Auto-recovers if the host goes down
|
|
217
|
+
```
|
|
218
|
+
|
|
208
219
|
## Timeouts
|
|
209
220
|
|
|
210
221
|
| Command | Default | Max | Flag |
|
package/README.md
CHANGED
|
@@ -116,6 +116,7 @@ mags run -p --url 'python3 -m http.server 8080'
|
|
|
116
116
|
| `-p, --persistent` | Keep VM alive after script completes | false |
|
|
117
117
|
| `--url` | Enable public URL access (requires -p) | false |
|
|
118
118
|
| `--port <port>` | Port to expose for URL access | 8080 |
|
|
119
|
+
| `--no-sleep` | Keep VM always running, never auto-sleep (requires -p) | false |
|
|
119
120
|
| `--startup-command <cmd>` | Command to run when VM wakes from sleep | none |
|
|
120
121
|
|
|
121
122
|
## SSH Access
|
|
@@ -219,6 +220,20 @@ mags run -w flask-app -p --url 'pip install flask && python app.py'
|
|
|
219
220
|
mags run -w api -p --url --startup-command 'python server.py' 'pip install -r requirements.txt && python server.py'
|
|
220
221
|
```
|
|
221
222
|
|
|
223
|
+
### Always-On VMs
|
|
224
|
+
|
|
225
|
+
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:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Always-on API server (never auto-sleeps)
|
|
229
|
+
mags run -w my-api -p --no-sleep --url --port 3000 'npm start'
|
|
230
|
+
|
|
231
|
+
# Always-on background worker
|
|
232
|
+
mags run -w worker -p --no-sleep 'python worker.py'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
If an always-on VM's host becomes unhealthy, the orchestrator automatically re-provisions it on a healthy agent within ~60 seconds.
|
|
236
|
+
|
|
222
237
|
### Interactive Development
|
|
223
238
|
|
|
224
239
|
```bash
|
|
@@ -257,6 +272,13 @@ const { requestId } = await mags.run('python script.py', {
|
|
|
257
272
|
startupCommand: 'python server.py'
|
|
258
273
|
});
|
|
259
274
|
|
|
275
|
+
// Always-on VM (never auto-sleeps)
|
|
276
|
+
await mags.run('node server.js', {
|
|
277
|
+
workspaceId: 'my-api',
|
|
278
|
+
persistent: true,
|
|
279
|
+
noSleep: true
|
|
280
|
+
});
|
|
281
|
+
|
|
260
282
|
// Get status
|
|
261
283
|
const status = await mags.status(requestId);
|
|
262
284
|
console.log(status);
|
|
@@ -298,7 +320,8 @@ curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
|
|
|
298
320
|
"script": "echo Hello World",
|
|
299
321
|
"type": "inline",
|
|
300
322
|
"workspace_id": "myproject",
|
|
301
|
-
"persistent": true
|
|
323
|
+
"persistent": true,
|
|
324
|
+
"no_sleep": true
|
|
302
325
|
}'
|
|
303
326
|
```
|
|
304
327
|
|
package/bin/mags.js
CHANGED
|
@@ -202,6 +202,7 @@ ${colors.bold}Commands:${colors.reset}
|
|
|
202
202
|
new <name> Create a new persistent VM (returns ID only)
|
|
203
203
|
run [options] <script> Execute a script on a microVM
|
|
204
204
|
ssh <workspace|name|id> Open SSH session (auto-starts VM if needed)
|
|
205
|
+
browser [workspace] Start a Chromium browser session (CDP access)
|
|
205
206
|
exec <workspace> <command> Run a command on an existing VM
|
|
206
207
|
status <name|id> Get job status
|
|
207
208
|
logs <name|id> Get job logs
|
|
@@ -218,8 +219,7 @@ ${colors.bold}Commands:${colors.reset}
|
|
|
218
219
|
setup-claude Install Mags skill for Claude Code
|
|
219
220
|
|
|
220
221
|
${colors.bold}Run Options:${colors.reset}
|
|
221
|
-
-
|
|
222
|
-
-n, --name <name> Set job name (for easier reference)
|
|
222
|
+
-n, --name <name> Set job/workspace name (used as both)
|
|
223
223
|
-p, --persistent Keep VM alive after script completes
|
|
224
224
|
--no-sleep Never auto-sleep this VM (requires -p)
|
|
225
225
|
--base <workspace> Mount workspace read-only as base image
|
|
@@ -240,7 +240,7 @@ ${colors.bold}Cron Commands:${colors.reset}
|
|
|
240
240
|
${colors.bold}Cron Options:${colors.reset}
|
|
241
241
|
--name <name> Cron job name (required)
|
|
242
242
|
--schedule <expr> Cron expression (required, e.g. "0 * * * *")
|
|
243
|
-
-w, --workspace <id> Workspace for cron jobs
|
|
243
|
+
-w, --workspace <id> Workspace for cron jobs (alias for --name)
|
|
244
244
|
-p, --persistent Keep VM alive after cron script
|
|
245
245
|
|
|
246
246
|
${colors.bold}Examples:${colors.reset}
|
|
@@ -251,11 +251,12 @@ ${colors.bold}Examples:${colors.reset}
|
|
|
251
251
|
mags run 'echo Hello World'
|
|
252
252
|
mags run -e 'echo fast' # Ephemeral (no S3 sync)
|
|
253
253
|
mags run -f script.py 'python3 script.py' # Upload + run file
|
|
254
|
-
mags run -
|
|
254
|
+
mags run -n myproject 'python3 script.py'
|
|
255
255
|
mags run --base golden 'npm test' # Use golden as read-only base
|
|
256
|
-
mags run --base golden -
|
|
256
|
+
mags run --base golden -n fork-1 'npm test' # Base + save changes to fork-1
|
|
257
257
|
mags run -p --url 'python3 -m http.server 8080'
|
|
258
|
-
mags run -n webapp -
|
|
258
|
+
mags run -n webapp -p --url --port 3000 'npm start'
|
|
259
|
+
mags browser myproject # Start browser with workspace
|
|
259
260
|
mags workspace list # List workspaces
|
|
260
261
|
mags workspace delete myproject # Delete workspace
|
|
261
262
|
mags cron add --name backup --schedule "0 0 * * *" 'tar czf backup.tar.gz data/'
|
|
@@ -467,7 +468,6 @@ async function newVM(args) {
|
|
|
467
468
|
|
|
468
469
|
async function runJob(args) {
|
|
469
470
|
let script = '';
|
|
470
|
-
let workspace = '';
|
|
471
471
|
let baseWorkspace = '';
|
|
472
472
|
let name = '';
|
|
473
473
|
let persistent = false;
|
|
@@ -484,8 +484,6 @@ async function runJob(args) {
|
|
|
484
484
|
switch (args[i]) {
|
|
485
485
|
case '-w':
|
|
486
486
|
case '--workspace':
|
|
487
|
-
workspace = args[++i];
|
|
488
|
-
break;
|
|
489
487
|
case '-n':
|
|
490
488
|
case '--name':
|
|
491
489
|
name = args[++i];
|
|
@@ -532,8 +530,8 @@ async function runJob(args) {
|
|
|
532
530
|
}
|
|
533
531
|
|
|
534
532
|
// Validate flag combinations
|
|
535
|
-
if (ephemeral &&
|
|
536
|
-
log('red', 'Error: Cannot use --ephemeral with --
|
|
533
|
+
if (ephemeral && name) {
|
|
534
|
+
log('red', 'Error: Cannot use --ephemeral with --name; ephemeral VMs have no persistent storage');
|
|
537
535
|
process.exit(1);
|
|
538
536
|
}
|
|
539
537
|
if (ephemeral && persistent) {
|
|
@@ -573,9 +571,11 @@ async function runJob(args) {
|
|
|
573
571
|
persistent
|
|
574
572
|
};
|
|
575
573
|
if (noSleep) payload.no_sleep = true;
|
|
576
|
-
//
|
|
577
|
-
if (
|
|
578
|
-
|
|
574
|
+
// name and workspace_id are always the same
|
|
575
|
+
if (name) {
|
|
576
|
+
payload.name = name;
|
|
577
|
+
if (!ephemeral) payload.workspace_id = name;
|
|
578
|
+
}
|
|
579
579
|
if (baseWorkspace) payload.base_workspace_id = baseWorkspace;
|
|
580
580
|
if (startupCommand) payload.startup_command = startupCommand;
|
|
581
581
|
if (fileIds.length > 0) payload.file_ids = fileIds;
|
|
@@ -592,12 +592,11 @@ async function runJob(args) {
|
|
|
592
592
|
const requestId = response.request_id;
|
|
593
593
|
log('green', `Job submitted: ${requestId}`);
|
|
594
594
|
if (name) log('blue', `Name: ${name}`);
|
|
595
|
-
if (workspace) log('blue', `Workspace: ${workspace}`);
|
|
596
595
|
if (baseWorkspace) log('blue', `Base workspace: ${baseWorkspace} (read-only)`);
|
|
597
596
|
if (persistent) log('yellow', 'Persistent: VM will stay alive');
|
|
598
597
|
|
|
599
|
-
// Poll for completion
|
|
600
|
-
const maxAttempts =
|
|
598
|
+
// Poll for completion (200ms intervals, 600 attempts = 2 min timeout)
|
|
599
|
+
const maxAttempts = 600;
|
|
601
600
|
let attempt = 0;
|
|
602
601
|
|
|
603
602
|
while (attempt < maxAttempts) {
|
|
@@ -610,7 +609,7 @@ async function runJob(args) {
|
|
|
610
609
|
// If --url requested, wait until VM is actually assigned (vm_id populated)
|
|
611
610
|
if (enableUrl && !status.vm_id) {
|
|
612
611
|
process.stdout.write('.');
|
|
613
|
-
await sleep(
|
|
612
|
+
await sleep(200);
|
|
614
613
|
attempt++;
|
|
615
614
|
continue;
|
|
616
615
|
}
|
|
@@ -634,7 +633,7 @@ async function runJob(args) {
|
|
|
634
633
|
}
|
|
635
634
|
|
|
636
635
|
process.stdout.write('.');
|
|
637
|
-
await sleep(
|
|
636
|
+
await sleep(200);
|
|
638
637
|
attempt++;
|
|
639
638
|
}
|
|
640
639
|
|
|
@@ -1289,6 +1288,114 @@ async function workspaceDelete(workspaceId) {
|
|
|
1289
1288
|
}
|
|
1290
1289
|
}
|
|
1291
1290
|
|
|
1291
|
+
async function browserSession(args) {
|
|
1292
|
+
let workspace = '';
|
|
1293
|
+
let name = '';
|
|
1294
|
+
|
|
1295
|
+
// Parse args: mags browser [workspace] [--name <n>]
|
|
1296
|
+
for (let i = 0; i < args.length; i++) {
|
|
1297
|
+
if ((args[i] === '-n' || args[i] === '--name') && args[i + 1]) {
|
|
1298
|
+
name = args[++i];
|
|
1299
|
+
} else if (!workspace) {
|
|
1300
|
+
workspace = args[i];
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Check for existing running/sleeping browser session with this workspace
|
|
1305
|
+
if (workspace) {
|
|
1306
|
+
const existingJob = await findWorkspaceJob(workspace);
|
|
1307
|
+
|
|
1308
|
+
if (existingJob && existingJob.status === 'running') {
|
|
1309
|
+
// Check if it's already a browser session
|
|
1310
|
+
const status = await request('GET', `/api/v1/mags-jobs/${existingJob.request_id}/status`);
|
|
1311
|
+
if (status.browser_mode && status.debug_ws_url) {
|
|
1312
|
+
log('green', 'Found existing browser session');
|
|
1313
|
+
console.log('');
|
|
1314
|
+
log('cyan', `CDP Endpoint: ${status.debug_ws_url}`);
|
|
1315
|
+
console.log('');
|
|
1316
|
+
log('gray', 'Connect with Playwright:');
|
|
1317
|
+
console.log(` const browser = await chromium.connectOverCDP('${status.debug_ws_url}');`);
|
|
1318
|
+
console.log('');
|
|
1319
|
+
log('gray', 'Connect with Puppeteer:');
|
|
1320
|
+
console.log(` const browser = await puppeteer.connect({ browserWSEndpoint: '${status.debug_ws_url}' });`);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
log('blue', 'Starting browser session...');
|
|
1327
|
+
|
|
1328
|
+
const payload = {
|
|
1329
|
+
script: 'echo "Browser session ready"',
|
|
1330
|
+
type: 'inline',
|
|
1331
|
+
browser_mode: true,
|
|
1332
|
+
persistent: true
|
|
1333
|
+
};
|
|
1334
|
+
if (workspace) payload.workspace_id = workspace;
|
|
1335
|
+
if (name) payload.name = name;
|
|
1336
|
+
if (!name && workspace) payload.name = `browser-${workspace}`;
|
|
1337
|
+
|
|
1338
|
+
const response = await request('POST', '/api/v1/mags-jobs', payload);
|
|
1339
|
+
|
|
1340
|
+
if (!response.request_id) {
|
|
1341
|
+
log('red', 'Failed to start browser session:');
|
|
1342
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1343
|
+
process.exit(1);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
const requestId = response.request_id;
|
|
1347
|
+
log('gray', `Job: ${requestId}`);
|
|
1348
|
+
|
|
1349
|
+
// Wait for VM + Chromium to be ready
|
|
1350
|
+
log('blue', 'Waiting for Chromium to start...');
|
|
1351
|
+
let status;
|
|
1352
|
+
for (let i = 0; i < 90; i++) {
|
|
1353
|
+
status = await request('GET', `/api/v1/mags-jobs/${requestId}/status`);
|
|
1354
|
+
if (status.status === 'running' && status.debug_ws_url) {
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
if (status.status === 'error') {
|
|
1358
|
+
log('red', `Browser session failed: ${status.error_message || 'Unknown error'}`);
|
|
1359
|
+
process.exit(1);
|
|
1360
|
+
}
|
|
1361
|
+
process.stdout.write('.');
|
|
1362
|
+
await sleep(1000);
|
|
1363
|
+
}
|
|
1364
|
+
console.log('');
|
|
1365
|
+
|
|
1366
|
+
if (!status || !status.debug_ws_url) {
|
|
1367
|
+
// VM is running but no CDP URL yet - it may still be initializing
|
|
1368
|
+
if (status && status.status === 'running' && status.subdomain) {
|
|
1369
|
+
const wsUrl = `wss://${status.subdomain}.apps.magpiecloud.com`;
|
|
1370
|
+
log('green', 'Browser session ready!');
|
|
1371
|
+
console.log('');
|
|
1372
|
+
log('cyan', `CDP Endpoint: ${wsUrl}`);
|
|
1373
|
+
console.log('');
|
|
1374
|
+
log('gray', 'Connect with Playwright:');
|
|
1375
|
+
console.log(` const browser = await chromium.connectOverCDP('${wsUrl}');`);
|
|
1376
|
+
console.log('');
|
|
1377
|
+
log('gray', 'Connect with Puppeteer:');
|
|
1378
|
+
console.log(` const browser = await puppeteer.connect({ browserWSEndpoint: '${wsUrl}' });`);
|
|
1379
|
+
} else {
|
|
1380
|
+
log('yellow', 'Browser session started but CDP URL not yet available.');
|
|
1381
|
+
log('yellow', `Check status with: mags status ${requestId}`);
|
|
1382
|
+
}
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
log('green', 'Browser session ready!');
|
|
1387
|
+
console.log('');
|
|
1388
|
+
log('cyan', `CDP Endpoint: ${status.debug_ws_url}`);
|
|
1389
|
+
console.log('');
|
|
1390
|
+
log('gray', 'Connect with Playwright:');
|
|
1391
|
+
console.log(` const browser = await chromium.connectOverCDP('${status.debug_ws_url}');`);
|
|
1392
|
+
console.log('');
|
|
1393
|
+
log('gray', 'Connect with Puppeteer:');
|
|
1394
|
+
console.log(` const browser = await puppeteer.connect({ browserWSEndpoint: '${status.debug_ws_url}' });`);
|
|
1395
|
+
console.log('');
|
|
1396
|
+
log('gray', `Stop with: mags stop ${workspace || requestId}`);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1292
1399
|
async function sshToJob(nameOrId) {
|
|
1293
1400
|
if (!nameOrId) {
|
|
1294
1401
|
log('red', 'Error: Workspace, job name, or job ID required');
|
|
@@ -1335,17 +1442,17 @@ async function sshToJob(nameOrId) {
|
|
|
1335
1442
|
jobID = response.request_id;
|
|
1336
1443
|
log('gray', `Job: ${jobID}`);
|
|
1337
1444
|
|
|
1338
|
-
// Wait for VM to be ready
|
|
1445
|
+
// Wait for VM to be ready (status=running AND vm_id assigned)
|
|
1339
1446
|
log('blue', 'Waiting for VM...');
|
|
1340
|
-
for (let i = 0; i <
|
|
1447
|
+
for (let i = 0; i < 60; i++) {
|
|
1341
1448
|
const status = await request('GET', `/api/v1/mags-jobs/${jobID}/status`);
|
|
1342
|
-
if (status.status === 'running') break;
|
|
1449
|
+
if (status.status === 'running' && status.vm_id) break;
|
|
1343
1450
|
if (status.status === 'error') {
|
|
1344
1451
|
log('red', 'VM failed to start');
|
|
1345
1452
|
process.exit(1);
|
|
1346
1453
|
}
|
|
1347
1454
|
process.stdout.write('.');
|
|
1348
|
-
await sleep(
|
|
1455
|
+
await sleep(300);
|
|
1349
1456
|
}
|
|
1350
1457
|
console.log('');
|
|
1351
1458
|
}
|
|
@@ -1521,7 +1628,7 @@ async function main() {
|
|
|
1521
1628
|
break;
|
|
1522
1629
|
case '--version':
|
|
1523
1630
|
case '-v':
|
|
1524
|
-
console.log('mags v1.8.
|
|
1631
|
+
console.log('mags v1.8.5');
|
|
1525
1632
|
process.exit(0);
|
|
1526
1633
|
break;
|
|
1527
1634
|
case 'new':
|
|
@@ -1536,6 +1643,10 @@ async function main() {
|
|
|
1536
1643
|
await requireAuth();
|
|
1537
1644
|
await sshToJob(args[1]);
|
|
1538
1645
|
break;
|
|
1646
|
+
case 'browser':
|
|
1647
|
+
await requireAuth();
|
|
1648
|
+
await browserSession(args.slice(1));
|
|
1649
|
+
break;
|
|
1539
1650
|
case 'exec':
|
|
1540
1651
|
await requireAuth();
|
|
1541
1652
|
await execOnJob(args[1], args.slice(2).join(' '));
|
package/package.json
CHANGED
package/python/README.md
CHANGED
|
@@ -66,6 +66,19 @@ m.run_and_wait(
|
|
|
66
66
|
)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
### Always-On VMs
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# VM that never auto-sleeps — stays running 24/7
|
|
73
|
+
job = m.run(
|
|
74
|
+
"python3 server.py",
|
|
75
|
+
workspace_id="my-api",
|
|
76
|
+
persistent=True,
|
|
77
|
+
no_sleep=True,
|
|
78
|
+
)
|
|
79
|
+
# Auto-recovers if the host goes down
|
|
80
|
+
```
|
|
81
|
+
|
|
69
82
|
### Enable URL / SSH Access
|
|
70
83
|
|
|
71
84
|
```python
|
|
@@ -116,8 +129,13 @@ print(f"Jobs: {usage['total_jobs']}, VM seconds: {usage['vm_seconds']:.0f}")
|
|
|
116
129
|
|
|
117
130
|
| Method | Description |
|
|
118
131
|
|--------|-------------|
|
|
119
|
-
| `run(script, **opts)` | Submit a job (
|
|
132
|
+
| `run(script, **opts)` | Submit a job (`persistent`, `no_sleep`, `workspace_id`, ...) |
|
|
120
133
|
| `run_and_wait(script, **opts)` | Submit and block until done |
|
|
134
|
+
| `new(name, **opts)` | Create a persistent VM workspace |
|
|
135
|
+
| `find_job(name_or_id)` | Find a running/sleeping job by name or workspace |
|
|
136
|
+
| `exec(name_or_id, command)` | Run a command on an existing VM via SSH |
|
|
137
|
+
| `stop(name_or_id)` | Stop a running job |
|
|
138
|
+
| `resize(workspace, disk_gb)` | Resize a workspace's disk |
|
|
121
139
|
| `status(request_id)` | Get job status |
|
|
122
140
|
| `logs(request_id)` | Get job logs |
|
|
123
141
|
| `list_jobs(page, page_size)` | List recent jobs |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/python/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: magpie-mags
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: Mags SDK - Execute scripts on Magpie's instant VM infrastructure
|
|
5
5
|
Author: Magpie Cloud
|
|
6
6
|
License: MIT
|
|
@@ -91,6 +91,19 @@ m.run_and_wait(
|
|
|
91
91
|
)
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### Always-On VMs
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# VM that never auto-sleeps — stays running 24/7
|
|
98
|
+
job = m.run(
|
|
99
|
+
"python3 server.py",
|
|
100
|
+
workspace_id="my-api",
|
|
101
|
+
persistent=True,
|
|
102
|
+
no_sleep=True,
|
|
103
|
+
)
|
|
104
|
+
# Auto-recovers if the host goes down
|
|
105
|
+
```
|
|
106
|
+
|
|
94
107
|
### Enable URL / SSH Access
|
|
95
108
|
|
|
96
109
|
```python
|
|
@@ -141,8 +154,13 @@ print(f"Jobs: {usage['total_jobs']}, VM seconds: {usage['vm_seconds']:.0f}")
|
|
|
141
154
|
|
|
142
155
|
| Method | Description |
|
|
143
156
|
|--------|-------------|
|
|
144
|
-
| `run(script, **opts)` | Submit a job (
|
|
157
|
+
| `run(script, **opts)` | Submit a job (`persistent`, `no_sleep`, `workspace_id`, ...) |
|
|
145
158
|
| `run_and_wait(script, **opts)` | Submit and block until done |
|
|
159
|
+
| `new(name, **opts)` | Create a persistent VM workspace |
|
|
160
|
+
| `find_job(name_or_id)` | Find a running/sleeping job by name or workspace |
|
|
161
|
+
| `exec(name_or_id, command)` | Run a command on an existing VM via SSH |
|
|
162
|
+
| `stop(name_or_id)` | Stop a running job |
|
|
163
|
+
| `resize(workspace, disk_gb)` | Resize a workspace's disk |
|
|
146
164
|
| `status(request_id)` | Get job status |
|
|
147
165
|
| `logs(request_id)` | Get job logs |
|
|
148
166
|
| `list_jobs(page, page_size)` | List recent jobs |
|
|
@@ -109,6 +109,20 @@ class Mags:
|
|
|
109
109
|
) -> dict:
|
|
110
110
|
"""Submit a job for execution.
|
|
111
111
|
|
|
112
|
+
Args:
|
|
113
|
+
script: Shell script to execute inside the VM.
|
|
114
|
+
name: Optional job name.
|
|
115
|
+
workspace_id: Persistent workspace name (synced to S3).
|
|
116
|
+
base_workspace_id: Read-only base workspace to mount.
|
|
117
|
+
persistent: Keep VM alive after script finishes.
|
|
118
|
+
no_sleep: Never auto-sleep this VM (requires persistent=True).
|
|
119
|
+
The VM stays running 24/7 and auto-recovers if its host goes down.
|
|
120
|
+
ephemeral: No S3 sync (faster, truly ephemeral).
|
|
121
|
+
startup_command: Command to run when VM wakes from sleep.
|
|
122
|
+
environment: Key-value env vars injected into the VM.
|
|
123
|
+
file_ids: File IDs to download into VM before script runs.
|
|
124
|
+
disk_gb: Custom disk size in GB (default 2GB).
|
|
125
|
+
|
|
112
126
|
Returns ``{"request_id": ..., "status": "accepted"}``.
|
|
113
127
|
"""
|
|
114
128
|
if ephemeral and workspace_id:
|
|
@@ -332,15 +346,7 @@ class Mags:
|
|
|
332
346
|
|
|
333
347
|
request_id = job.get("request_id") or job.get("id")
|
|
334
348
|
|
|
335
|
-
#
|
|
336
|
-
for _ in range(15):
|
|
337
|
-
st = self.status(request_id)
|
|
338
|
-
if st.get("vm_id"):
|
|
339
|
-
break
|
|
340
|
-
time.sleep(1)
|
|
341
|
-
else:
|
|
342
|
-
raise MagsError(f"VM for '{name_or_id}' has no vm_id after 15s")
|
|
343
|
-
|
|
349
|
+
# Call /access directly — for sleeping VMs this triggers the wake
|
|
344
350
|
access = self.enable_access(request_id, port=22)
|
|
345
351
|
|
|
346
352
|
if not access.get("success") or not access.get("ssh_host"):
|
package/website/api.html
CHANGED
|
@@ -109,11 +109,10 @@
|
|
|
109
109
|
</div>
|
|
110
110
|
<nav class="nav-links">
|
|
111
111
|
<a href="index.html">Home</a>
|
|
112
|
-
<a href="index.html#quickstart">
|
|
113
|
-
<a href="index.html#sdk">SDKs</a>
|
|
112
|
+
<a href="index.html#quickstart">Docs</a>
|
|
114
113
|
<a href="cookbook.html">Cookbook</a>
|
|
114
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
115
115
|
<a href="login.html">Login</a>
|
|
116
|
-
<a href="tokens.html">Tokens</a>
|
|
117
116
|
</nav>
|
|
118
117
|
<div class="nav-cta">
|
|
119
118
|
<a class="button ghost" href="tokens.html">Get API token</a>
|
|
@@ -924,11 +923,10 @@ mags cron remove <id></code></pre>
|
|
|
924
923
|
</div>
|
|
925
924
|
<div class="footer-links">
|
|
926
925
|
<a href="index.html">Home</a>
|
|
926
|
+
<a href="cookbook.html">Cookbook</a>
|
|
927
927
|
<a href="https://pypi.org/project/magpie-mags/" rel="noreferrer">Python SDK</a>
|
|
928
928
|
<a href="https://www.npmjs.com/package/@magpiecloud/mags" rel="noreferrer">Node.js SDK</a>
|
|
929
|
-
<a href="
|
|
930
|
-
<a href="tokens.html">Tokens</a>
|
|
931
|
-
<a href="usage.html">Usage</a>
|
|
929
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
932
930
|
<a href="login.html">Login</a>
|
|
933
931
|
</div>
|
|
934
932
|
</div>
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<nav class="nav-links">
|
|
26
26
|
<a href="index.html">Home</a>
|
|
27
|
-
<a href="index.html#
|
|
28
|
-
<a href="api.html">API
|
|
27
|
+
<a href="index.html#quickstart">Docs</a>
|
|
28
|
+
<a href="api.html">API</a>
|
|
29
29
|
<a href="cookbook.html">Cookbook</a>
|
|
30
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
30
31
|
<a href="login.html">Login</a>
|
|
31
32
|
</nav>
|
|
32
33
|
<div class="nav-cta">
|
|
@@ -467,13 +468,10 @@ Use workspace "monitors" so the log persists</code></pre>
|
|
|
467
468
|
</div>
|
|
468
469
|
<div class="footer-links">
|
|
469
470
|
<a href="index.html">Home</a>
|
|
470
|
-
<a href="
|
|
471
|
-
<a href="usage.html">Usage</a>
|
|
472
|
-
<a href="tokens.html">Tokens</a>
|
|
471
|
+
<a href="api.html">API</a>
|
|
473
472
|
<a href="cookbook.html">Cookbook</a>
|
|
474
|
-
<a href="https://
|
|
475
|
-
<a href="
|
|
476
|
-
<a href="https://github.com/magpiecloud/mags" rel="noreferrer">GitHub</a>
|
|
473
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
474
|
+
<a href="login.html">Login</a>
|
|
477
475
|
</div>
|
|
478
476
|
</div>
|
|
479
477
|
</footer>
|
|
@@ -1,50 +1,42 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# hn-marketing.sh — Fetch top HN stories and
|
|
2
|
+
# hn-marketing.sh — Fetch top 10 HN stories and save to CSV
|
|
3
3
|
# Dependencies: curl, jq (install with: apk add -q curl jq)
|
|
4
4
|
|
|
5
5
|
set -e
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
CSV="${HN_CSV:-./hn-top10.csv}"
|
|
8
|
+
TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
echo ""
|
|
12
|
-
|
|
13
|
-
IDS=$(curl -sf "https://hacker-news.firebaseio.com/v0/topstories.json" | jq -r '.[0:100] | .[]')
|
|
14
|
-
|
|
15
|
-
if [ -z "$IDS" ]; then
|
|
16
|
-
echo "Error: could not fetch story IDs"
|
|
17
|
-
exit 1
|
|
10
|
+
# Write CSV header if file doesn't exist
|
|
11
|
+
if [ ! -f "$CSV" ]; then
|
|
12
|
+
echo "timestamp,rank,score,title,url,hn_link" > "$CSV"
|
|
18
13
|
fi
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
echo "=== HN Top 10 ==="
|
|
16
|
+
echo "Fetched at: $TIMESTAMP"
|
|
17
|
+
echo ""
|
|
18
|
+
|
|
19
|
+
# Get top 10 story IDs
|
|
20
|
+
IDS=$(curl -sf "https://hacker-news.firebaseio.com/v0/topstories.json" | jq -r '.[0:10] | .[]')
|
|
21
21
|
|
|
22
|
+
RANK=0
|
|
22
23
|
for ID in $IDS; do
|
|
24
|
+
RANK=$((RANK + 1))
|
|
23
25
|
ITEM=$(curl -sf "https://hacker-news.firebaseio.com/v0/item/${ID}.json")
|
|
24
|
-
TITLE=$(echo "$ITEM" | jq -r '.title // empty')
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
TITLE=$(echo "$ITEM" | jq -r '.title // "untitled"' | tr -d '\n\r' | sed 's/,/;/g')
|
|
28
|
+
URL=$(echo "$ITEM" | jq -r '.url // ""' | tr -d '\n\r')
|
|
29
|
+
SCORE=$(echo "$ITEM" | jq -r '.score // 0')
|
|
30
|
+
HN_LINK="https://news.ycombinator.com/item?id=$ID"
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
[ -z "$URL" ] && URL="$HN_LINK"
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
TIME=$(echo "$ITEM" | jq -r '.time // 0')
|
|
36
|
-
COUNT=$((COUNT + 1))
|
|
34
|
+
echo "[$RANK] $TITLE"
|
|
35
|
+
echo " Score: $SCORE | $URL"
|
|
36
|
+
echo ""
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
echo " Score: $SCORE | Link: $URL"
|
|
40
|
-
echo " HN: https://news.ycombinator.com/item?id=$ID"
|
|
41
|
-
echo ""
|
|
42
|
-
fi
|
|
38
|
+
echo "$TIMESTAMP,$RANK,$SCORE,$TITLE,$URL,$HN_LINK" >> "$CSV"
|
|
43
39
|
done
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else
|
|
48
|
-
echo "---"
|
|
49
|
-
echo "Found $COUNT marketing-related stories."
|
|
50
|
-
fi
|
|
41
|
+
echo "---"
|
|
42
|
+
echo "Saved to $CSV"
|
package/website/cookbook.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/>
|
|
11
11
|
<meta name="api-base" content="https://api.magpiecloud.com" />
|
|
12
12
|
<meta name="auth-base" content="https://api.magpiecloud.com" />
|
|
13
|
-
<link rel="stylesheet" href="styles.css?v=
|
|
13
|
+
<link rel="stylesheet" href="styles.css?v=6" />
|
|
14
14
|
<script src="env.js"></script>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -24,13 +24,10 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<nav class="nav-links">
|
|
26
26
|
<a href="index.html">Home</a>
|
|
27
|
-
<a href="index.html#quickstart">
|
|
28
|
-
<a href="
|
|
29
|
-
<a href="
|
|
27
|
+
<a href="index.html#quickstart">Docs</a>
|
|
28
|
+
<a href="api.html">API</a>
|
|
29
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
30
30
|
<a href="login.html">Login</a>
|
|
31
|
-
<a href="usage.html">Usage</a>
|
|
32
|
-
<a href="tokens.html">Tokens</a>
|
|
33
|
-
<a href="claude-skill.html">Claude Skill</a>
|
|
34
31
|
</nav>
|
|
35
32
|
<div class="nav-cta">
|
|
36
33
|
<a class="button ghost" href="index.html#quickstart">Get started</a>
|
|
@@ -171,18 +168,25 @@ mags logs <job-id></code></pre>
|
|
|
171
168
|
</article>
|
|
172
169
|
<article class="recipe-item" data-reveal>
|
|
173
170
|
<div class="recipe-header">
|
|
174
|
-
<h3>HN
|
|
171
|
+
<h3>HN Top 10 Digest</h3>
|
|
175
172
|
<span class="pill">Cron</span>
|
|
176
173
|
</div>
|
|
177
|
-
<pre><code># Upload script + install deps
|
|
178
|
-
mags run -f
|
|
179
|
-
'
|
|
174
|
+
<pre><code># Upload script + install deps (file lands in /root/)
|
|
175
|
+
mags run -f hn-marketing.sh -w hn-digest \
|
|
176
|
+
'chmod +x /root/hn-marketing.sh && apk add -q curl jq'
|
|
177
|
+
|
|
178
|
+
# Run it once (saves to /root/hn-top10.csv)
|
|
179
|
+
mags run -w hn-digest 'HN_CSV=/root/hn-top10.csv sh /root/hn-marketing.sh'
|
|
180
180
|
|
|
181
|
-
#
|
|
182
|
-
mags cron add --name "hn-
|
|
181
|
+
# Schedule it (every 2 hours, results append to CSV)
|
|
182
|
+
mags cron add --name "hn-top10" \
|
|
183
183
|
--schedule "0 */2 * * *" -w hn-digest \
|
|
184
|
-
'sh /root/hn-marketing.sh'</code></pre>
|
|
185
|
-
<p class="label"
|
|
184
|
+
'HN_CSV=/root/hn-top10.csv sh /root/hn-marketing.sh'</code></pre>
|
|
185
|
+
<p class="label">
|
|
186
|
+
<a href="cookbook/hn-marketing.sh" download>Download hn-marketing.sh</a>
|
|
187
|
+
·
|
|
188
|
+
<a href="cookbook/hn-marketing.html">Full recipe + live demo →</a>
|
|
189
|
+
</p>
|
|
186
190
|
</article>
|
|
187
191
|
</div>
|
|
188
192
|
</div>
|
|
@@ -264,11 +268,10 @@ curl -X POST https://api.magpiecloud.com/api/v1/mags-cron \
|
|
|
264
268
|
</div>
|
|
265
269
|
<div class="footer-links">
|
|
266
270
|
<a href="index.html">Home</a>
|
|
267
|
-
<a href="
|
|
268
|
-
<a href="usage.html">Usage</a>
|
|
269
|
-
<a href="tokens.html">Tokens</a>
|
|
271
|
+
<a href="api.html">API</a>
|
|
270
272
|
<a href="claude-skill.html">Claude Skill</a>
|
|
271
|
-
<a href="https://
|
|
273
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
274
|
+
<a href="login.html">Login</a>
|
|
272
275
|
</div>
|
|
273
276
|
</div>
|
|
274
277
|
</footer>
|
package/website/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/>
|
|
11
11
|
<meta name="api-base" content="https://api.magpiecloud.com" />
|
|
12
12
|
<meta name="auth-base" content="https://api.magpiecloud.com" />
|
|
13
|
-
<link rel="stylesheet" href="styles.css?v=
|
|
13
|
+
<link rel="stylesheet" href="styles.css?v=6" />
|
|
14
14
|
<script src="env.js"></script>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -23,11 +23,10 @@
|
|
|
23
23
|
<span class="tag">Secure cloud sandboxes for the AI age</span>
|
|
24
24
|
</div>
|
|
25
25
|
<nav class="nav-links">
|
|
26
|
-
<a href="#
|
|
27
|
-
<a href="
|
|
28
|
-
<a href="
|
|
29
|
-
<a href="
|
|
30
|
-
<a href="api.html">API Docs</a>
|
|
26
|
+
<a href="#quickstart">Docs</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>
|
|
31
30
|
<a href="login.html" id="nav-auth-link">Login</a>
|
|
32
31
|
</nav>
|
|
33
32
|
<div class="nav-cta">
|
|
@@ -41,22 +40,16 @@
|
|
|
41
40
|
<section class="hero" id="overview">
|
|
42
41
|
<div class="container hero-grid">
|
|
43
42
|
<div class="hero-copy">
|
|
44
|
-
<span class="pill">New: Python SDK</span>
|
|
45
43
|
<h1>Secure sandboxes that boot in milliseconds. Your data stays.</h1>
|
|
46
44
|
<p class="lead">
|
|
47
|
-
Give your AI agents
|
|
48
|
-
completely isolated, boots
|
|
49
|
-
automatically.
|
|
50
|
-
needs to run without risk.
|
|
45
|
+
Give your AI agents an instant home. Every sandbox is
|
|
46
|
+
completely isolated, boots in ~300ms, and syncs your files to the cloud
|
|
47
|
+
automatically. CLI, Python, and Node.js — pick your tool.
|
|
51
48
|
</p>
|
|
52
49
|
<div class="hero-actions">
|
|
53
50
|
<a class="button" href="#quickstart">Quickstart</a>
|
|
54
51
|
<a class="button ghost" href="api.html">API Reference</a>
|
|
55
52
|
</div>
|
|
56
|
-
<div class="hero-meta">
|
|
57
|
-
<span>CLI, Python, and Node.js — pick your tool.</span>
|
|
58
|
-
<span>Sandboxes in ~300ms. Files persist to the cloud between runs.</span>
|
|
59
|
-
</div>
|
|
60
53
|
</div>
|
|
61
54
|
<div class="hero-card tab-group" aria-label="Mags example">
|
|
62
55
|
<div class="tab-bar">
|
|
@@ -513,6 +506,45 @@ await mags.cronCreate({
|
|
|
513
506
|
</div>
|
|
514
507
|
</section>
|
|
515
508
|
|
|
509
|
+
<!-- ── Always-On Servers ──────────────────────────── -->
|
|
510
|
+
<section class="section" id="always-on">
|
|
511
|
+
<div class="container">
|
|
512
|
+
<div class="section-title">
|
|
513
|
+
<p>Always-On Servers</p>
|
|
514
|
+
<h2>Keep your sandboxes running forever.</h2>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="grid split">
|
|
517
|
+
<article class="panel" data-reveal>
|
|
518
|
+
<h3>Never auto-sleep</h3>
|
|
519
|
+
<p>By default, persistent sandboxes auto-sleep after 10 minutes of inactivity to save resources. With the <code>--no-sleep</code> flag, your VM stays running 24/7 — perfect for web servers, workers, and background processes.</p>
|
|
520
|
+
<pre><code># CLI
|
|
521
|
+
mags run -w my-api -p --no-sleep --url --port 3000 'node server.js'
|
|
522
|
+
|
|
523
|
+
# Python
|
|
524
|
+
m.run("node server.js",
|
|
525
|
+
workspace_id="my-api", persistent=True, no_sleep=True)
|
|
526
|
+
|
|
527
|
+
# Node.js
|
|
528
|
+
await mags.run('node server.js', {
|
|
529
|
+
workspaceId: 'my-api', persistent: true, noSleep: true,
|
|
530
|
+
});</code></pre>
|
|
531
|
+
</article>
|
|
532
|
+
<article class="panel" data-reveal>
|
|
533
|
+
<h3>Auto-recovery</h3>
|
|
534
|
+
<p>Always-on sandboxes are automatically monitored. If the host goes down, your VM is re-provisioned on a healthy server within ~60 seconds — no manual intervention needed.</p>
|
|
535
|
+
<h3 style="margin-top:1.2rem">How it works</h3>
|
|
536
|
+
<ul class="list">
|
|
537
|
+
<li>Requires <code>-p</code> (persistent) flag</li>
|
|
538
|
+
<li>VM stays in <code>running</code> state indefinitely</li>
|
|
539
|
+
<li>Combine with <code>--url</code> to expose a public HTTPS endpoint</li>
|
|
540
|
+
<li>Use <code>--startup-command</code> to auto-restart your process if the VM recovers</li>
|
|
541
|
+
<li>Files persist to the cloud via workspace sync</li>
|
|
542
|
+
</ul>
|
|
543
|
+
</article>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
</section>
|
|
547
|
+
|
|
516
548
|
<!-- ── SDKs + API ──────────────────────────────────── -->
|
|
517
549
|
<section class="section" id="sdk">
|
|
518
550
|
<div class="container">
|
|
@@ -692,6 +724,7 @@ console.log(result.logs);</code></pre>
|
|
|
692
724
|
<a href="api.html">API Reference</a>
|
|
693
725
|
<a href="claude-skill.html">Claude Skill</a>
|
|
694
726
|
<a href="cookbook.html">Cookbook</a>
|
|
727
|
+
<a href="https://discord.gg/3avpC2nS" rel="noreferrer" target="_blank">Discord</a>
|
|
695
728
|
<a href="https://github.com/magpiecloud/mags/issues" rel="noreferrer">Issues</a>
|
|
696
729
|
</div>
|
|
697
730
|
</div>
|
|
@@ -704,8 +737,8 @@ console.log(result.logs);</code></pre>
|
|
|
704
737
|
if (token) {
|
|
705
738
|
var nav = document.getElementById('nav-auth-link');
|
|
706
739
|
var cta = document.getElementById('cta-auth-link');
|
|
707
|
-
if (nav) { nav.textContent = '
|
|
708
|
-
if (cta) { cta.textContent = 'Dashboard'; cta.href = '
|
|
740
|
+
if (nav) { nav.textContent = 'Usage'; nav.href = 'usage.html'; }
|
|
741
|
+
if (cta) { cta.textContent = 'Dashboard'; cta.href = 'usage.html'; }
|
|
709
742
|
}
|
|
710
743
|
})();
|
|
711
744
|
</script>
|
package/website/login.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Mags Login</title>
|
|
7
|
-
<meta name="description" content="Sign in to Mags with Google
|
|
7
|
+
<meta name="description" content="Sign in to Mags with Google." />
|
|
8
8
|
<meta name="api-base" content="https://api.magpiecloud.com" />
|
|
9
9
|
<meta name="auth-base" content="https://api.magpiecloud.com" />
|
|
10
10
|
<link rel="stylesheet" href="styles.css?v=2" />
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
</div>
|
|
22
22
|
<nav class="nav-links">
|
|
23
23
|
<a href="index.html">Home</a>
|
|
24
|
-
<a href="index.html#quickstart">
|
|
25
|
-
<a href="
|
|
26
|
-
<a href="
|
|
24
|
+
<a href="index.html#quickstart">Docs</a>
|
|
25
|
+
<a href="api.html">API</a>
|
|
26
|
+
<a href="cookbook.html">Cookbook</a>
|
|
27
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
27
28
|
</nav>
|
|
28
29
|
<div class="nav-cta">
|
|
29
30
|
<a class="button ghost" href="index.html">Back to home</a>
|
|
@@ -37,30 +38,10 @@
|
|
|
37
38
|
<div>
|
|
38
39
|
<span class="pill">Login</span>
|
|
39
40
|
<h1 class="auth-title">Sign in to Mags.</h1>
|
|
40
|
-
<p class="auth-subtitle">
|
|
41
|
+
<p class="auth-subtitle">Sign in with your Google account to get started.</p>
|
|
41
42
|
</div>
|
|
42
43
|
|
|
43
44
|
<button class="button full" type="button" data-google-login>Continue with Google</button>
|
|
44
|
-
|
|
45
|
-
<div class="auth-divider"><span>or</span></div>
|
|
46
|
-
|
|
47
|
-
<form class="login-form" data-login-form>
|
|
48
|
-
<div class="form-group">
|
|
49
|
-
<label class="form-label" for="login-email">Email</label>
|
|
50
|
-
<input class="input" type="email" id="login-email" name="email" required />
|
|
51
|
-
</div>
|
|
52
|
-
<div class="form-group">
|
|
53
|
-
<label class="form-label" for="login-password">Password</label>
|
|
54
|
-
<input class="input" type="password" id="login-password" name="password" required />
|
|
55
|
-
</div>
|
|
56
|
-
<button class="button full" type="submit">Sign in</button>
|
|
57
|
-
<p class="form-message" data-login-message></p>
|
|
58
|
-
</form>
|
|
59
|
-
|
|
60
|
-
<div class="form-links">
|
|
61
|
-
<a data-auth-link="/auth/forgot-password" href="#">Forgot password?</a>
|
|
62
|
-
<a data-auth-link="/auth/register" href="#">Create an account</a>
|
|
63
|
-
</div>
|
|
64
45
|
</div>
|
|
65
46
|
</div>
|
|
66
47
|
</main>
|
|
@@ -76,9 +57,9 @@
|
|
|
76
57
|
</div>
|
|
77
58
|
<div class="footer-links">
|
|
78
59
|
<a href="index.html">Home</a>
|
|
79
|
-
<a href="
|
|
80
|
-
<a href="
|
|
81
|
-
<a href="
|
|
60
|
+
<a href="api.html">API</a>
|
|
61
|
+
<a href="cookbook.html">Cookbook</a>
|
|
62
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
82
63
|
</div>
|
|
83
64
|
</div>
|
|
84
65
|
</footer>
|
package/website/tokens.html
CHANGED
|
@@ -24,13 +24,10 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<nav class="nav-links">
|
|
26
26
|
<a href="index.html">Home</a>
|
|
27
|
-
<a href="index.html#quickstart">Quickstart</a>
|
|
28
|
-
<a href="index.html#cli">CLI</a>
|
|
29
|
-
<a href="index.html#api">API</a>
|
|
30
|
-
<a href="login.html">Login</a>
|
|
31
27
|
<a href="usage.html">Usage</a>
|
|
32
|
-
<a href="
|
|
28
|
+
<a href="api.html">API</a>
|
|
33
29
|
<a href="cookbook.html">Cookbook</a>
|
|
30
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
34
31
|
</nav>
|
|
35
32
|
<div class="nav-cta">
|
|
36
33
|
<a class="button ghost" href="/api-keys">Manage tokens</a>
|
|
@@ -158,10 +155,10 @@ mags whoami</code></pre>
|
|
|
158
155
|
</div>
|
|
159
156
|
<div class="footer-links">
|
|
160
157
|
<a href="index.html">Home</a>
|
|
161
|
-
<a href="login.html">Login</a>
|
|
162
158
|
<a href="usage.html">Usage</a>
|
|
163
|
-
<a href="
|
|
159
|
+
<a href="api.html">API</a>
|
|
164
160
|
<a href="cookbook.html">Cookbook</a>
|
|
161
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
165
162
|
</div>
|
|
166
163
|
</div>
|
|
167
164
|
</footer>
|
package/website/usage.html
CHANGED
|
@@ -24,13 +24,10 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<nav class="nav-links">
|
|
26
26
|
<a href="index.html">Home</a>
|
|
27
|
-
<a href="index.html#quickstart">Quickstart</a>
|
|
28
|
-
<a href="index.html#cli">CLI</a>
|
|
29
|
-
<a href="index.html#api">API</a>
|
|
30
|
-
<a href="login.html">Login</a>
|
|
31
27
|
<a href="tokens.html">Tokens</a>
|
|
32
|
-
<a href="
|
|
28
|
+
<a href="api.html">API</a>
|
|
33
29
|
<a href="cookbook.html">Cookbook</a>
|
|
30
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
34
31
|
</nav>
|
|
35
32
|
<div class="nav-cta">
|
|
36
33
|
<a class="button ghost" href="/mags">Open console</a>
|
|
@@ -174,10 +171,10 @@ mags cron add --name "backup" \
|
|
|
174
171
|
</div>
|
|
175
172
|
<div class="footer-links">
|
|
176
173
|
<a href="index.html">Home</a>
|
|
177
|
-
<a href="login.html">Login</a>
|
|
178
174
|
<a href="tokens.html">Tokens</a>
|
|
179
|
-
<a href="
|
|
175
|
+
<a href="api.html">API</a>
|
|
180
176
|
<a href="cookbook.html">Cookbook</a>
|
|
177
|
+
<a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
|
|
181
178
|
</div>
|
|
182
179
|
</div>
|
|
183
180
|
</footer>
|
|
Binary file
|
|
Binary file
|