@magpiecloud/mags 1.5.1 → 1.6.0
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 +161 -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/nodejs/index.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mags SDK - Execute scripts on Magpie's instant VM infrastructure
|
|
3
|
+
* @module @magpiecloud/mags
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const { URL } = require('url');
|
|
9
|
+
|
|
10
|
+
class Mags {
|
|
11
|
+
/**
|
|
12
|
+
* Create a Mags client
|
|
13
|
+
* @param {object} options - Configuration options
|
|
14
|
+
* @param {string} options.apiUrl - API endpoint (default: https://api.magpiecloud.com)
|
|
15
|
+
* @param {string} options.apiToken - API token (required, or set MAGS_API_TOKEN env var)
|
|
16
|
+
*/
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.apiUrl = options.apiUrl || process.env.MAGS_API_URL || 'https://api.magpiecloud.com';
|
|
19
|
+
this.apiToken = options.apiToken || process.env.MAGS_API_TOKEN;
|
|
20
|
+
|
|
21
|
+
if (!this.apiToken) {
|
|
22
|
+
throw new Error('API token required. Set MAGS_API_TOKEN or pass apiToken option.');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_request(method, path, body = null) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const url = new URL(path, this.apiUrl);
|
|
29
|
+
const isHttps = url.protocol === 'https:';
|
|
30
|
+
const lib = isHttps ? https : http;
|
|
31
|
+
|
|
32
|
+
const options = {
|
|
33
|
+
hostname: url.hostname,
|
|
34
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
35
|
+
path: url.pathname + url.search,
|
|
36
|
+
method,
|
|
37
|
+
headers: {
|
|
38
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
39
|
+
'Content-Type': 'application/json'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const req = lib.request(options, (res) => {
|
|
44
|
+
let data = '';
|
|
45
|
+
res.on('data', chunk => data += chunk);
|
|
46
|
+
res.on('end', () => {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(data);
|
|
49
|
+
if (res.statusCode >= 400) {
|
|
50
|
+
reject(new Error(parsed.error || parsed.message || `HTTP ${res.statusCode}`));
|
|
51
|
+
} else {
|
|
52
|
+
resolve(parsed);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
resolve(data);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
req.on('error', reject);
|
|
61
|
+
if (body) req.write(JSON.stringify(body));
|
|
62
|
+
req.end();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Submit a job for execution
|
|
68
|
+
* @param {string} script - Script to execute
|
|
69
|
+
* @param {object} options - Job options
|
|
70
|
+
* @param {string} options.name - Job name
|
|
71
|
+
* @param {string} options.workspaceId - Persistent workspace ID
|
|
72
|
+
* @param {boolean} options.persistent - Keep VM alive after script
|
|
73
|
+
* @param {boolean} options.ephemeral - No workspace/S3 sync (fastest)
|
|
74
|
+
* @param {string} options.startupCommand - Command to run when waking from sleep
|
|
75
|
+
* @param {object} options.environment - Environment variables
|
|
76
|
+
* @param {string[]} options.fileIds - File IDs from uploadFiles()
|
|
77
|
+
* @returns {Promise<{requestId: string, status: string}>}
|
|
78
|
+
*/
|
|
79
|
+
async run(script, options = {}) {
|
|
80
|
+
if (options.ephemeral && options.workspaceId) {
|
|
81
|
+
throw new Error('Cannot use ephemeral with workspaceId');
|
|
82
|
+
}
|
|
83
|
+
if (options.ephemeral && options.persistent) {
|
|
84
|
+
throw new Error('Cannot use ephemeral with persistent');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const payload = {
|
|
88
|
+
script,
|
|
89
|
+
type: 'inline',
|
|
90
|
+
name: options.name,
|
|
91
|
+
persistent: options.persistent || false,
|
|
92
|
+
startup_command: options.startupCommand,
|
|
93
|
+
environment: options.environment
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Only set workspace_id if not ephemeral
|
|
97
|
+
if (!options.ephemeral) {
|
|
98
|
+
payload.workspace_id = options.workspaceId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (options.fileIds && options.fileIds.length > 0) {
|
|
102
|
+
payload.file_ids = options.fileIds;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const response = await this._request('POST', '/api/v1/mags-jobs', payload);
|
|
106
|
+
return {
|
|
107
|
+
requestId: response.request_id,
|
|
108
|
+
status: response.status
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get job status
|
|
114
|
+
* @param {string} requestId - Job request ID
|
|
115
|
+
* @returns {Promise<object>}
|
|
116
|
+
*/
|
|
117
|
+
async status(requestId) {
|
|
118
|
+
return this._request('GET', `/api/v1/mags-jobs/${requestId}/status`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get job logs
|
|
123
|
+
* @param {string} requestId - Job request ID
|
|
124
|
+
* @returns {Promise<{logs: Array}>}
|
|
125
|
+
*/
|
|
126
|
+
async logs(requestId) {
|
|
127
|
+
return this._request('GET', `/api/v1/mags-jobs/${requestId}/logs`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* List recent jobs
|
|
132
|
+
* @param {object} options - Pagination options
|
|
133
|
+
* @param {number} options.page - Page number (default: 1)
|
|
134
|
+
* @param {number} options.pageSize - Page size (default: 20)
|
|
135
|
+
* @returns {Promise<{jobs: Array, total: number}>}
|
|
136
|
+
*/
|
|
137
|
+
async list(options = {}) {
|
|
138
|
+
const page = options.page || 1;
|
|
139
|
+
const pageSize = options.pageSize || 20;
|
|
140
|
+
return this._request('GET', `/api/v1/mags-jobs?page=${page}&page_size=${pageSize}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Enable URL access for a job
|
|
145
|
+
* @param {string} requestId - Job request ID
|
|
146
|
+
* @param {number} port - Port to expose (default: 8080)
|
|
147
|
+
* @returns {Promise<object>}
|
|
148
|
+
*/
|
|
149
|
+
async enableUrl(requestId, port = 8080) {
|
|
150
|
+
return this._request('POST', `/api/v1/mags-jobs/${requestId}/access`, { port });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Stop a running job
|
|
155
|
+
* @param {string} requestId - Job request ID
|
|
156
|
+
* @returns {Promise<object>}
|
|
157
|
+
*/
|
|
158
|
+
async stop(requestId) {
|
|
159
|
+
return this._request('POST', `/api/v1/mags-jobs/${requestId}/stop`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Run a job and wait for completion
|
|
164
|
+
* @param {string} script - Script to execute
|
|
165
|
+
* @param {object} options - Job options
|
|
166
|
+
* @param {number} options.timeout - Timeout in ms (default: 60000)
|
|
167
|
+
* @returns {Promise<{status: string, exitCode: number, logs: Array}>}
|
|
168
|
+
*/
|
|
169
|
+
async runAndWait(script, options = {}) {
|
|
170
|
+
const timeout = options.timeout || 60000;
|
|
171
|
+
const { requestId } = await this.run(script, options);
|
|
172
|
+
|
|
173
|
+
const startTime = Date.now();
|
|
174
|
+
while (Date.now() - startTime < timeout) {
|
|
175
|
+
const status = await this.status(requestId);
|
|
176
|
+
|
|
177
|
+
if (status.status === 'completed' || status.status === 'error') {
|
|
178
|
+
const logsResp = await this.logs(requestId);
|
|
179
|
+
return {
|
|
180
|
+
requestId,
|
|
181
|
+
status: status.status,
|
|
182
|
+
exitCode: status.exit_code,
|
|
183
|
+
durationMs: status.script_duration_ms,
|
|
184
|
+
logs: logsResp.logs || []
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
throw new Error(`Job ${requestId} timed out after ${timeout}ms`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Upload files for use in a job
|
|
196
|
+
* @param {string[]} filePaths - Array of local file paths
|
|
197
|
+
* @returns {Promise<string[]>} Array of file IDs
|
|
198
|
+
*/
|
|
199
|
+
async uploadFiles(filePaths) {
|
|
200
|
+
const fs = require('fs');
|
|
201
|
+
const path = require('path');
|
|
202
|
+
const fileIds = [];
|
|
203
|
+
|
|
204
|
+
for (const filePath of filePaths) {
|
|
205
|
+
const fileName = path.basename(filePath);
|
|
206
|
+
const fileData = fs.readFileSync(filePath);
|
|
207
|
+
const boundary = '----MagsBoundary' + Date.now().toString(16);
|
|
208
|
+
|
|
209
|
+
const parts = [];
|
|
210
|
+
parts.push(`--${boundary}\r\n`);
|
|
211
|
+
parts.push(`Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`);
|
|
212
|
+
parts.push(`Content-Type: application/octet-stream\r\n\r\n`);
|
|
213
|
+
const header = Buffer.from(parts.join(''));
|
|
214
|
+
const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
215
|
+
const body = Buffer.concat([header, fileData, footer]);
|
|
216
|
+
|
|
217
|
+
const response = await this._multipartRequest('/api/v1/mags-files', body, boundary);
|
|
218
|
+
if (response.file_id) {
|
|
219
|
+
fileIds.push(response.file_id);
|
|
220
|
+
} else {
|
|
221
|
+
throw new Error(`Failed to upload file: ${fileName}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return fileIds;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_multipartRequest(apiPath, body, boundary) {
|
|
229
|
+
return new Promise((resolve, reject) => {
|
|
230
|
+
const url = new URL(apiPath, this.apiUrl);
|
|
231
|
+
const isHttps = url.protocol === 'https:';
|
|
232
|
+
const lib = isHttps ? https : http;
|
|
233
|
+
|
|
234
|
+
const options = {
|
|
235
|
+
hostname: url.hostname,
|
|
236
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
237
|
+
path: url.pathname,
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: {
|
|
240
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
241
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
242
|
+
'Content-Length': body.length
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const req = lib.request(options, (res) => {
|
|
247
|
+
let data = '';
|
|
248
|
+
res.on('data', chunk => data += chunk);
|
|
249
|
+
res.on('end', () => {
|
|
250
|
+
try {
|
|
251
|
+
resolve(JSON.parse(data));
|
|
252
|
+
} catch {
|
|
253
|
+
resolve(data);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
req.on('error', reject);
|
|
259
|
+
req.write(body);
|
|
260
|
+
req.end();
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Cron job methods
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Create a cron job
|
|
268
|
+
* @param {object} options - Cron job options
|
|
269
|
+
* @param {string} options.name - Cron job name
|
|
270
|
+
* @param {string} options.cronExpression - Cron expression (e.g., "0 * * * *")
|
|
271
|
+
* @param {string} options.script - Script to execute
|
|
272
|
+
* @param {string} options.workspaceId - Workspace ID
|
|
273
|
+
* @param {boolean} options.persistent - Keep VM alive
|
|
274
|
+
* @returns {Promise<object>}
|
|
275
|
+
*/
|
|
276
|
+
async cronCreate(options) {
|
|
277
|
+
const payload = {
|
|
278
|
+
name: options.name,
|
|
279
|
+
cron_expression: options.cronExpression,
|
|
280
|
+
script: options.script,
|
|
281
|
+
workspace_id: options.workspaceId,
|
|
282
|
+
persistent: options.persistent || false
|
|
283
|
+
};
|
|
284
|
+
return this._request('POST', '/api/v1/mags-cron', payload);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* List cron jobs
|
|
289
|
+
* @returns {Promise<{cron_jobs: Array}>}
|
|
290
|
+
*/
|
|
291
|
+
async cronList() {
|
|
292
|
+
return this._request('GET', '/api/v1/mags-cron');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get a cron job
|
|
297
|
+
* @param {string} id - Cron job ID
|
|
298
|
+
* @returns {Promise<object>}
|
|
299
|
+
*/
|
|
300
|
+
async cronGet(id) {
|
|
301
|
+
return this._request('GET', `/api/v1/mags-cron/${id}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Update a cron job
|
|
306
|
+
* @param {string} id - Cron job ID
|
|
307
|
+
* @param {object} updates - Fields to update
|
|
308
|
+
* @returns {Promise<object>}
|
|
309
|
+
*/
|
|
310
|
+
async cronUpdate(id, updates) {
|
|
311
|
+
return this._request('PATCH', `/api/v1/mags-cron/${id}`, updates);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Delete a cron job
|
|
316
|
+
* @param {string} id - Cron job ID
|
|
317
|
+
* @returns {Promise<object>}
|
|
318
|
+
*/
|
|
319
|
+
async cronDelete(id) {
|
|
320
|
+
return this._request('DELETE', `/api/v1/mags-cron/${id}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = Mags;
|
|
325
|
+
module.exports.Mags = Mags;
|
|
326
|
+
module.exports.default = Mags;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@magpiecloud/mags",
|
|
3
|
+
"version": "1.5.1",
|
|
4
|
+
"description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mags": "./bin/mags.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/mags.js --help"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"magpie",
|
|
14
|
+
"mags",
|
|
15
|
+
"vm",
|
|
16
|
+
"microvm",
|
|
17
|
+
"cloud",
|
|
18
|
+
"serverless",
|
|
19
|
+
"execution",
|
|
20
|
+
"cli",
|
|
21
|
+
"claude",
|
|
22
|
+
"claude-code"
|
|
23
|
+
],
|
|
24
|
+
"author": "Magpie Cloud",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/magpiecloud/mags"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://mags.run",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/magpiecloud/mags/issues"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=14.0.0"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"index.js",
|
|
39
|
+
"bin/mags.js",
|
|
40
|
+
"README.md"
|
|
41
|
+
]
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpiecloud/mags",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,10 +16,7 @@
|
|
|
16
16
|
"microvm",
|
|
17
17
|
"cloud",
|
|
18
18
|
"serverless",
|
|
19
|
-
"execution"
|
|
20
|
-
"cli",
|
|
21
|
-
"claude",
|
|
22
|
-
"claude-code"
|
|
19
|
+
"execution"
|
|
23
20
|
],
|
|
24
21
|
"author": "Magpie Cloud",
|
|
25
22
|
"license": "MIT",
|
|
@@ -27,16 +24,8 @@
|
|
|
27
24
|
"type": "git",
|
|
28
25
|
"url": "https://github.com/magpiecloud/mags"
|
|
29
26
|
},
|
|
30
|
-
"homepage": "https://mags
|
|
31
|
-
"bugs": {
|
|
32
|
-
"url": "https://github.com/magpiecloud/mags/issues"
|
|
33
|
-
},
|
|
27
|
+
"homepage": "https://magpiecloud.com/docs/mags",
|
|
34
28
|
"engines": {
|
|
35
29
|
"node": ">=14.0.0"
|
|
36
|
-
}
|
|
37
|
-
"files": [
|
|
38
|
-
"index.js",
|
|
39
|
-
"bin/mags.js",
|
|
40
|
-
"README.md"
|
|
41
|
-
]
|
|
30
|
+
}
|
|
42
31
|
}
|