@magpiecloud/mags 1.4.2 → 1.5.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/bin/mags.js +277 -2
- package/index.js +147 -1
- package/package.json +1 -1
package/bin/mags.js
CHANGED
|
@@ -192,18 +192,37 @@ ${colors.bold}Run Options:${colors.reset}
|
|
|
192
192
|
-w, --workspace <id> Use persistent workspace (S3 sync)
|
|
193
193
|
-n, --name <name> Set job name (for easier reference)
|
|
194
194
|
-p, --persistent Keep VM alive after script completes
|
|
195
|
+
-e, --ephemeral No workspace/S3 sync (fastest execution)
|
|
196
|
+
-f, --file <path> Upload file(s) to VM (repeatable)
|
|
195
197
|
--url Enable public URL access (requires -p)
|
|
196
198
|
--port <port> Port to expose for URL (default: 8080)
|
|
197
199
|
--startup-command <cmd> Command to run when VM wakes from sleep
|
|
198
200
|
|
|
201
|
+
${colors.bold}Cron Commands:${colors.reset}
|
|
202
|
+
cron add [options] <script> Create a scheduled cron job
|
|
203
|
+
cron list List all cron jobs
|
|
204
|
+
cron remove <id> Delete a cron job
|
|
205
|
+
cron enable <id> Enable a cron job
|
|
206
|
+
cron disable <id> Disable a cron job
|
|
207
|
+
|
|
208
|
+
${colors.bold}Cron Options:${colors.reset}
|
|
209
|
+
--name <name> Cron job name (required)
|
|
210
|
+
--schedule <expr> Cron expression (required, e.g. "0 * * * *")
|
|
211
|
+
-w, --workspace <id> Workspace for cron jobs
|
|
212
|
+
-p, --persistent Keep VM alive after cron script
|
|
213
|
+
|
|
199
214
|
${colors.bold}Examples:${colors.reset}
|
|
200
215
|
mags login
|
|
201
216
|
mags new myvm # Create VM, get ID
|
|
202
217
|
mags ssh myvm # SSH by name
|
|
203
218
|
mags run 'echo Hello World'
|
|
219
|
+
mags run -e 'echo fast' # Ephemeral (no S3 sync)
|
|
220
|
+
mags run -f script.py 'python3 script.py' # Upload + run file
|
|
204
221
|
mags run -w myproject 'python3 script.py'
|
|
205
222
|
mags run -p --url 'python3 -m http.server 8080'
|
|
206
223
|
mags run -n webapp -w webapp -p --url --port 3000 'npm start'
|
|
224
|
+
mags cron add --name backup --schedule "0 0 * * *" 'tar czf backup.tar.gz data/'
|
|
225
|
+
mags cron list
|
|
207
226
|
mags status myvm
|
|
208
227
|
mags logs myvm
|
|
209
228
|
mags url myvm 8080
|
|
@@ -394,9 +413,11 @@ async function runJob(args) {
|
|
|
394
413
|
let workspace = '';
|
|
395
414
|
let name = '';
|
|
396
415
|
let persistent = false;
|
|
416
|
+
let ephemeral = false;
|
|
397
417
|
let enableUrl = false;
|
|
398
418
|
let port = 8080;
|
|
399
419
|
let startupCommand = '';
|
|
420
|
+
let fileArgs = [];
|
|
400
421
|
|
|
401
422
|
// Parse flags
|
|
402
423
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -413,6 +434,14 @@ async function runJob(args) {
|
|
|
413
434
|
case '--persistent':
|
|
414
435
|
persistent = true;
|
|
415
436
|
break;
|
|
437
|
+
case '-e':
|
|
438
|
+
case '--ephemeral':
|
|
439
|
+
ephemeral = true;
|
|
440
|
+
break;
|
|
441
|
+
case '-f':
|
|
442
|
+
case '--file':
|
|
443
|
+
fileArgs.push(args[++i]);
|
|
444
|
+
break;
|
|
416
445
|
case '--url':
|
|
417
446
|
enableUrl = true;
|
|
418
447
|
break;
|
|
@@ -433,6 +462,32 @@ async function runJob(args) {
|
|
|
433
462
|
usage();
|
|
434
463
|
}
|
|
435
464
|
|
|
465
|
+
// Validate flag combinations
|
|
466
|
+
if (ephemeral && workspace) {
|
|
467
|
+
log('red', 'Error: Cannot use --ephemeral with --workspace; ephemeral VMs have no persistent storage');
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
if (ephemeral && persistent) {
|
|
471
|
+
log('red', 'Error: Cannot use --ephemeral with --persistent; ephemeral VMs are destroyed after execution');
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Upload files if any
|
|
476
|
+
let fileIds = [];
|
|
477
|
+
if (fileArgs.length > 0) {
|
|
478
|
+
for (const filePath of fileArgs) {
|
|
479
|
+
log('blue', `Uploading ${filePath}...`);
|
|
480
|
+
const fileId = await uploadFile(filePath);
|
|
481
|
+
if (fileId) {
|
|
482
|
+
fileIds.push(fileId);
|
|
483
|
+
log('green', `Uploaded: ${filePath} (${fileId})`);
|
|
484
|
+
} else {
|
|
485
|
+
log('red', `Failed to upload: ${filePath}`);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
436
491
|
log('blue', 'Submitting job...');
|
|
437
492
|
|
|
438
493
|
const payload = {
|
|
@@ -440,9 +495,11 @@ async function runJob(args) {
|
|
|
440
495
|
type: 'inline',
|
|
441
496
|
persistent
|
|
442
497
|
};
|
|
443
|
-
|
|
498
|
+
// Only set workspace_id if not ephemeral
|
|
499
|
+
if (!ephemeral && workspace) payload.workspace_id = workspace;
|
|
444
500
|
if (name) payload.name = name;
|
|
445
501
|
if (startupCommand) payload.startup_command = startupCommand;
|
|
502
|
+
if (fileIds.length > 0) payload.file_ids = fileIds;
|
|
446
503
|
|
|
447
504
|
const response = await request('POST', '/api/v1/mags-jobs', payload);
|
|
448
505
|
|
|
@@ -681,6 +738,220 @@ scripts on instant VMs directly from Claude.
|
|
|
681
738
|
}
|
|
682
739
|
}
|
|
683
740
|
|
|
741
|
+
// Upload file via multipart form data
|
|
742
|
+
async function uploadFile(filePath) {
|
|
743
|
+
if (!fs.existsSync(filePath)) {
|
|
744
|
+
log('red', `File not found: ${filePath}`);
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const fileName = path.basename(filePath);
|
|
749
|
+
const fileData = fs.readFileSync(filePath);
|
|
750
|
+
const boundary = '----MagsBoundary' + Date.now().toString(16);
|
|
751
|
+
|
|
752
|
+
// Build multipart body
|
|
753
|
+
const parts = [];
|
|
754
|
+
parts.push(`--${boundary}\r\n`);
|
|
755
|
+
parts.push(`Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`);
|
|
756
|
+
parts.push(`Content-Type: application/octet-stream\r\n\r\n`);
|
|
757
|
+
const header = Buffer.from(parts.join(''));
|
|
758
|
+
const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
759
|
+
const body = Buffer.concat([header, fileData, footer]);
|
|
760
|
+
|
|
761
|
+
return new Promise((resolve, reject) => {
|
|
762
|
+
const url = new URL('/api/v1/mags-files', API_URL);
|
|
763
|
+
const isHttps = url.protocol === 'https:';
|
|
764
|
+
const lib = isHttps ? https : http;
|
|
765
|
+
|
|
766
|
+
const options = {
|
|
767
|
+
hostname: url.hostname,
|
|
768
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
769
|
+
path: url.pathname,
|
|
770
|
+
method: 'POST',
|
|
771
|
+
headers: {
|
|
772
|
+
'Authorization': `Bearer ${API_TOKEN}`,
|
|
773
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
774
|
+
'Content-Length': body.length
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
const req = lib.request(options, (res) => {
|
|
779
|
+
let data = '';
|
|
780
|
+
res.on('data', chunk => data += chunk);
|
|
781
|
+
res.on('end', () => {
|
|
782
|
+
try {
|
|
783
|
+
const parsed = JSON.parse(data);
|
|
784
|
+
if (parsed.file_id) {
|
|
785
|
+
resolve(parsed.file_id);
|
|
786
|
+
} else {
|
|
787
|
+
resolve(null);
|
|
788
|
+
}
|
|
789
|
+
} catch {
|
|
790
|
+
resolve(null);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
req.on('error', () => resolve(null));
|
|
796
|
+
req.write(body);
|
|
797
|
+
req.end();
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Cron job management
|
|
802
|
+
async function cronCommand(args) {
|
|
803
|
+
if (args.length === 0) {
|
|
804
|
+
log('red', 'Error: Cron subcommand required (add, list, remove, enable, disable)');
|
|
805
|
+
usage();
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const subcommand = args[0];
|
|
810
|
+
const subArgs = args.slice(1);
|
|
811
|
+
|
|
812
|
+
switch (subcommand) {
|
|
813
|
+
case 'add':
|
|
814
|
+
await cronAdd(subArgs);
|
|
815
|
+
break;
|
|
816
|
+
case 'list':
|
|
817
|
+
case 'ls':
|
|
818
|
+
await cronList();
|
|
819
|
+
break;
|
|
820
|
+
case 'remove':
|
|
821
|
+
case 'rm':
|
|
822
|
+
case 'delete':
|
|
823
|
+
if (!subArgs[0]) {
|
|
824
|
+
log('red', 'Error: Cron job ID required');
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
await cronRemove(subArgs[0]);
|
|
828
|
+
break;
|
|
829
|
+
case 'enable':
|
|
830
|
+
if (!subArgs[0]) {
|
|
831
|
+
log('red', 'Error: Cron job ID required');
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
await cronToggle(subArgs[0], true);
|
|
835
|
+
break;
|
|
836
|
+
case 'disable':
|
|
837
|
+
if (!subArgs[0]) {
|
|
838
|
+
log('red', 'Error: Cron job ID required');
|
|
839
|
+
process.exit(1);
|
|
840
|
+
}
|
|
841
|
+
await cronToggle(subArgs[0], false);
|
|
842
|
+
break;
|
|
843
|
+
default:
|
|
844
|
+
log('red', `Unknown cron subcommand: ${subcommand}`);
|
|
845
|
+
usage();
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
async function cronAdd(args) {
|
|
850
|
+
let cronName = '';
|
|
851
|
+
let schedule = '';
|
|
852
|
+
let workspace = '';
|
|
853
|
+
let persistent = false;
|
|
854
|
+
let script = '';
|
|
855
|
+
|
|
856
|
+
for (let i = 0; i < args.length; i++) {
|
|
857
|
+
switch (args[i]) {
|
|
858
|
+
case '--name':
|
|
859
|
+
cronName = args[++i];
|
|
860
|
+
break;
|
|
861
|
+
case '--schedule':
|
|
862
|
+
schedule = args[++i];
|
|
863
|
+
break;
|
|
864
|
+
case '-w':
|
|
865
|
+
case '--workspace':
|
|
866
|
+
workspace = args[++i];
|
|
867
|
+
break;
|
|
868
|
+
case '-p':
|
|
869
|
+
case '--persistent':
|
|
870
|
+
persistent = true;
|
|
871
|
+
break;
|
|
872
|
+
default:
|
|
873
|
+
script = args.slice(i).join(' ');
|
|
874
|
+
i = args.length;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (!cronName) {
|
|
879
|
+
log('red', 'Error: --name is required for cron jobs');
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
if (!schedule) {
|
|
883
|
+
log('red', 'Error: --schedule is required (e.g. "0 * * * *")');
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
886
|
+
if (!script) {
|
|
887
|
+
log('red', 'Error: Script is required');
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const payload = {
|
|
892
|
+
name: cronName,
|
|
893
|
+
cron_expression: schedule,
|
|
894
|
+
script,
|
|
895
|
+
persistent
|
|
896
|
+
};
|
|
897
|
+
if (workspace) payload.workspace_id = workspace;
|
|
898
|
+
|
|
899
|
+
const resp = await request('POST', '/api/v1/mags-cron', payload);
|
|
900
|
+
if (resp.id) {
|
|
901
|
+
log('green', `Cron job created: ${resp.id}`);
|
|
902
|
+
log('blue', `Name: ${cronName}`);
|
|
903
|
+
log('blue', `Schedule: ${schedule}`);
|
|
904
|
+
if (resp.next_run_at) log('blue', `Next run: ${resp.next_run_at}`);
|
|
905
|
+
} else {
|
|
906
|
+
log('red', 'Failed to create cron job:');
|
|
907
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
908
|
+
process.exit(1);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
async function cronList() {
|
|
913
|
+
const resp = await request('GET', '/api/v1/mags-cron');
|
|
914
|
+
if (resp.cron_jobs && resp.cron_jobs.length > 0) {
|
|
915
|
+
log('cyan', 'Cron Jobs:\n');
|
|
916
|
+
resp.cron_jobs.forEach(cron => {
|
|
917
|
+
const statusColor = cron.enabled ? 'green' : 'yellow';
|
|
918
|
+
console.log(`${colors.gray}${cron.id}${colors.reset}`);
|
|
919
|
+
console.log(` Name: ${cron.name}`);
|
|
920
|
+
console.log(` Schedule: ${cron.cron_expression}`);
|
|
921
|
+
console.log(` Enabled: ${colors[statusColor]}${cron.enabled}${colors.reset}`);
|
|
922
|
+
console.log(` Workspace: ${cron.workspace_id || '-'}`);
|
|
923
|
+
console.log(` Runs: ${cron.run_count || 0}`);
|
|
924
|
+
console.log(` Last Run: ${cron.last_run_at || '-'}`);
|
|
925
|
+
console.log(` Next Run: ${cron.next_run_at || '-'}`);
|
|
926
|
+
console.log(` Last Status: ${cron.last_status || '-'}`);
|
|
927
|
+
console.log('');
|
|
928
|
+
});
|
|
929
|
+
} else {
|
|
930
|
+
log('yellow', 'No cron jobs found');
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
async function cronRemove(id) {
|
|
935
|
+
const resp = await request('DELETE', `/api/v1/mags-cron/${id}`);
|
|
936
|
+
if (resp.success) {
|
|
937
|
+
log('green', 'Cron job deleted');
|
|
938
|
+
} else {
|
|
939
|
+
log('red', 'Failed to delete cron job');
|
|
940
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async function cronToggle(id, enabled) {
|
|
945
|
+
const resp = await request('PATCH', `/api/v1/mags-cron/${id}`, { enabled });
|
|
946
|
+
if (resp.id) {
|
|
947
|
+
log('green', `Cron job ${enabled ? 'enabled' : 'disabled'}`);
|
|
948
|
+
if (resp.next_run_at) log('blue', `Next run: ${resp.next_run_at}`);
|
|
949
|
+
} else {
|
|
950
|
+
log('red', `Failed to ${enabled ? 'enable' : 'disable'} cron job`);
|
|
951
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
684
955
|
async function sshToJob(nameOrId) {
|
|
685
956
|
if (!nameOrId) {
|
|
686
957
|
log('red', 'Error: Job name or ID required');
|
|
@@ -811,7 +1082,7 @@ async function main() {
|
|
|
811
1082
|
break;
|
|
812
1083
|
case '--version':
|
|
813
1084
|
case '-v':
|
|
814
|
-
console.log('mags v1.
|
|
1085
|
+
console.log('mags v1.5.1');
|
|
815
1086
|
process.exit(0);
|
|
816
1087
|
break;
|
|
817
1088
|
case 'new':
|
|
@@ -846,6 +1117,10 @@ async function main() {
|
|
|
846
1117
|
await requireAuth();
|
|
847
1118
|
await stopJob(args[1]);
|
|
848
1119
|
break;
|
|
1120
|
+
case 'cron':
|
|
1121
|
+
await requireAuth();
|
|
1122
|
+
await cronCommand(args.slice(1));
|
|
1123
|
+
break;
|
|
849
1124
|
case 'setup-claude':
|
|
850
1125
|
await setupClaude();
|
|
851
1126
|
break;
|
package/index.js
CHANGED
|
@@ -70,21 +70,38 @@ class Mags {
|
|
|
70
70
|
* @param {string} options.name - Job name
|
|
71
71
|
* @param {string} options.workspaceId - Persistent workspace ID
|
|
72
72
|
* @param {boolean} options.persistent - Keep VM alive after script
|
|
73
|
+
* @param {boolean} options.ephemeral - No workspace/S3 sync (fastest)
|
|
73
74
|
* @param {string} options.startupCommand - Command to run when waking from sleep
|
|
74
75
|
* @param {object} options.environment - Environment variables
|
|
76
|
+
* @param {string[]} options.fileIds - File IDs from uploadFiles()
|
|
75
77
|
* @returns {Promise<{requestId: string, status: string}>}
|
|
76
78
|
*/
|
|
77
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
|
+
|
|
78
87
|
const payload = {
|
|
79
88
|
script,
|
|
80
89
|
type: 'inline',
|
|
81
90
|
name: options.name,
|
|
82
|
-
workspace_id: options.workspaceId,
|
|
83
91
|
persistent: options.persistent || false,
|
|
84
92
|
startup_command: options.startupCommand,
|
|
85
93
|
environment: options.environment
|
|
86
94
|
};
|
|
87
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
|
+
|
|
88
105
|
const response = await this._request('POST', '/api/v1/mags-jobs', payload);
|
|
89
106
|
return {
|
|
90
107
|
requestId: response.request_id,
|
|
@@ -173,6 +190,135 @@ class Mags {
|
|
|
173
190
|
|
|
174
191
|
throw new Error(`Job ${requestId} timed out after ${timeout}ms`);
|
|
175
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
|
+
}
|
|
176
322
|
}
|
|
177
323
|
|
|
178
324
|
module.exports = Mags;
|