@magpiecloud/mags 1.8.3 → 1.8.4

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 CHANGED
@@ -207,6 +207,9 @@ ${colors.bold}Commands:${colors.reset}
207
207
  logs <name|id> Get job logs
208
208
  list List recent jobs
209
209
  url <name|id> [port] Enable URL access for a job
210
+ url alias <sub> <workspace> Create a stable URL alias for a workspace
211
+ url alias list List your URL aliases
212
+ url alias remove <subdomain> Delete a URL alias
210
213
  stop <name|id> Stop a running job
211
214
  resize <workspace> --disk <GB> Resize a workspace's disk (restarts VM)
212
215
  sync <workspace|id> Sync workspace to S3 (without stopping)
@@ -260,6 +263,10 @@ ${colors.bold}Examples:${colors.reset}
260
263
  mags status myvm
261
264
  mags logs myvm
262
265
  mags url myvm 8080
266
+ mags url alias my-api myvm # Stable URL: my-api.apps.magpiecloud.com
267
+ mags url alias my-api myvm --lfg # Stable URL: my-api.app.lfg.run
268
+ mags url alias list # List all aliases
269
+ mags url alias remove my-api # Remove alias
263
270
  mags setup-claude # Install Claude Code skill
264
271
  `);
265
272
  process.exit(1);
@@ -668,6 +675,98 @@ async function enableUrlAccess(nameOrId, port = 8080) {
668
675
  }
669
676
  }
670
677
 
678
+ async function urlAliasCommand(args) {
679
+ if (args.length === 0) {
680
+ log('red', 'Error: URL alias subcommand required');
681
+ console.log('\nUsage:');
682
+ console.log(' mags url alias <subdomain> <workspace> [--lfg]');
683
+ console.log(' mags url alias list');
684
+ console.log(' mags url alias remove <subdomain>');
685
+ process.exit(1);
686
+ }
687
+
688
+ const subcommand = args[0];
689
+
690
+ switch (subcommand) {
691
+ case 'list':
692
+ case 'ls':
693
+ await urlAliasList();
694
+ break;
695
+ case 'remove':
696
+ case 'rm':
697
+ case 'delete':
698
+ if (!args[1]) {
699
+ log('red', 'Error: Subdomain required');
700
+ console.log('\nUsage: mags url alias remove <subdomain>');
701
+ process.exit(1);
702
+ }
703
+ await urlAliasRemove(args[1]);
704
+ break;
705
+ default:
706
+ // mags url alias <subdomain> <workspace> [--lfg]
707
+ const subdomain = args[0];
708
+ const workspace = args[1];
709
+ if (!workspace) {
710
+ log('red', 'Error: Workspace required');
711
+ console.log('\nUsage: mags url alias <subdomain> <workspace> [--lfg]');
712
+ process.exit(1);
713
+ }
714
+ const useLfg = args.includes('--lfg');
715
+ await urlAliasCreate(subdomain, workspace, useLfg);
716
+ break;
717
+ }
718
+ }
719
+
720
+ async function urlAliasCreate(subdomain, workspaceId, useLfg) {
721
+ const domain = useLfg ? 'app.lfg.run' : 'apps.magpiecloud.com';
722
+ log('blue', `Creating URL alias: ${subdomain}.${domain} → workspace '${workspaceId}'...`);
723
+
724
+ const resp = await request('POST', '/api/v1/mags-url-aliases', {
725
+ subdomain,
726
+ workspace_id: workspaceId,
727
+ domain,
728
+ });
729
+
730
+ if (resp.error) {
731
+ log('red', `Error: ${resp.error}`);
732
+ process.exit(1);
733
+ }
734
+
735
+ if (resp.url) {
736
+ log('green', `URL alias created: ${resp.url}`);
737
+ } else {
738
+ log('green', `URL alias created: https://${subdomain}.${domain}`);
739
+ }
740
+ }
741
+
742
+ async function urlAliasList() {
743
+ const resp = await request('GET', '/api/v1/mags-url-aliases');
744
+ const aliases = resp.aliases || [];
745
+
746
+ if (aliases.length > 0) {
747
+ log('cyan', 'URL Aliases:\n');
748
+ aliases.forEach(a => {
749
+ console.log(` ${colors.bold}${a.subdomain}${colors.reset}`);
750
+ console.log(` URL: ${colors.green}${a.url}${colors.reset}`);
751
+ console.log(` Workspace: ${a.workspace_id} Domain: ${a.domain}`);
752
+ console.log('');
753
+ });
754
+ log('gray', `Total: ${aliases.length} alias(es)`);
755
+ } else {
756
+ log('yellow', 'No URL aliases found');
757
+ }
758
+ }
759
+
760
+ async function urlAliasRemove(subdomain) {
761
+ log('blue', `Removing URL alias '${subdomain}'...`);
762
+ const resp = await request('DELETE', `/api/v1/mags-url-aliases/${subdomain}`);
763
+ if (resp.error) {
764
+ log('red', `Error: ${resp.error}`);
765
+ process.exit(1);
766
+ }
767
+ log('green', resp.message || `URL alias '${subdomain}' removed`);
768
+ }
769
+
671
770
  async function getStatus(nameOrId) {
672
771
  if (!nameOrId) {
673
772
  log('red', 'Error: Job name or ID required');
@@ -1443,7 +1542,11 @@ async function main() {
1443
1542
  break;
1444
1543
  case 'url':
1445
1544
  await requireAuth();
1446
- await enableUrlAccess(args[1], parseInt(args[2]) || 8080);
1545
+ if (args[1] === 'alias') {
1546
+ await urlAliasCommand(args.slice(2));
1547
+ } else {
1548
+ await enableUrlAccess(args[1], parseInt(args[2]) || 8080);
1549
+ }
1447
1550
  break;
1448
1551
  case 'status':
1449
1552
  await requireAuth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpiecloud/mags",
3
- "version": "1.8.3",
3
+ "version": "1.8.4",
4
4
  "description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "magpie-mags"
7
- version = "1.3.0"
7
+ version = "1.3.1"
8
8
  description = "Mags SDK - Execute scripts on Magpie's instant VM infrastructure"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -507,3 +507,36 @@ class Mags:
507
507
  def cron_delete(self, cron_id: str) -> dict:
508
508
  """Delete a cron job."""
509
509
  return self._request("DELETE", f"/mags-cron/{cron_id}")
510
+
511
+ # ── URL aliases ──────────────────────────────────────────────────
512
+
513
+ def url_alias_create(
514
+ self,
515
+ subdomain: str,
516
+ workspace_id: str,
517
+ domain: str = "apps.magpiecloud.com",
518
+ ) -> dict:
519
+ """Create a stable URL alias for a workspace.
520
+
521
+ The alias maps ``subdomain.<domain>`` to the active job in the workspace.
522
+ Use ``domain="app.lfg.run"`` for the LFG domain.
523
+
524
+ Returns ``{"id": ..., "subdomain": ..., "url": ...}``.
525
+ """
526
+ return self._request(
527
+ "POST",
528
+ "/mags-url-aliases",
529
+ json={
530
+ "subdomain": subdomain,
531
+ "workspace_id": workspace_id,
532
+ "domain": domain,
533
+ },
534
+ )
535
+
536
+ def url_alias_list(self) -> dict:
537
+ """List all URL aliases. Returns ``{"aliases": [...], "total": N}``."""
538
+ return self._request("GET", "/mags-url-aliases")
539
+
540
+ def url_alias_delete(self, subdomain: str) -> dict:
541
+ """Delete a URL alias by subdomain."""
542
+ return self._request("DELETE", f"/mags-url-aliases/{subdomain}")