@magpiecloud/mags 1.3.1 → 1.4.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.
Files changed (3) hide show
  1. package/README.md +55 -4
  2. package/bin/mags.js +102 -4
  3. package/package.json +5 -3
package/README.md CHANGED
@@ -18,7 +18,14 @@ mags login
18
18
 
19
19
  This will open your browser to create an API token. Paste the token when prompted, and it will be saved for future use.
20
20
 
21
- ### 2. Run a script
21
+ ### 2. Create a persistent VM
22
+
23
+ ```bash
24
+ mags new myproject
25
+ mags ssh myproject
26
+ ```
27
+
28
+ ### 3. Or run a script
22
29
 
23
30
  ```bash
24
31
  mags run 'echo Hello World'
@@ -26,8 +33,6 @@ mags run 'echo Hello World'
26
33
 
27
34
  ## Authentication
28
35
 
29
- The CLI supports multiple authentication methods:
30
-
31
36
  ### Interactive Login (Recommended)
32
37
 
33
38
  ```bash
@@ -54,7 +59,13 @@ export MAGS_API_TOKEN="your-token-here"
54
59
  ## CLI Commands
55
60
 
56
61
  ```bash
57
- # Simple command
62
+ # Create a persistent VM
63
+ mags new myproject
64
+
65
+ # SSH into it
66
+ mags ssh myproject
67
+
68
+ # Run a simple command
58
69
  mags run 'echo Hello'
59
70
 
60
71
  # With persistent workspace (S3 sync)
@@ -78,8 +89,26 @@ mags status <job-id>
78
89
  mags logs <job-id>
79
90
  mags list
80
91
  mags stop <job-id>
92
+
93
+ # Install Claude Code skill
94
+ mags setup-claude
81
95
  ```
82
96
 
97
+ ## Claude Code Integration
98
+
99
+ Install the Mags skill for Claude Code to run scripts directly from Claude:
100
+
101
+ ```bash
102
+ mags setup-claude
103
+ ```
104
+
105
+ This lets you use natural language commands like:
106
+ - `/mags run echo Hello World`
107
+ - `/mags create a python environment with numpy and pandas`
108
+ - `/mags run a flask server and give me the public URL`
109
+
110
+ See [mags.run/claude-skill.html](https://mags.run/claude-skill.html) for full documentation.
111
+
83
112
  ## CLI Flags
84
113
 
85
114
  | Flag | Description | Default |
@@ -90,6 +119,27 @@ mags stop <job-id>
90
119
  | `--port` | Port to expose for URL access | 8080 |
91
120
  | `--startup-command` | Command when VM wakes from sleep | none |
92
121
 
122
+ ## SSH Access
123
+
124
+ Connect to any running VM:
125
+
126
+ ```bash
127
+ mags ssh myproject
128
+ ```
129
+
130
+ Features:
131
+ - Connects via secure proxy (`api.magpiecloud.com:PORT`)
132
+ - Full PTY support for interactive terminals
133
+ - Automatic SSH key management
134
+ - Hostname: `mags-vm`
135
+
136
+ ## Workspaces & Persistence
137
+
138
+ Your `/root` directory automatically syncs to S3:
139
+ - **Auto-sync**: Every 30 seconds while running
140
+ - **On stop**: Full sync before VM terminates
141
+ - **On wake**: Previous state restored
142
+
93
143
  ## Node.js SDK
94
144
 
95
145
  ```javascript
@@ -134,6 +184,7 @@ await mags.stop(requestId);
134
184
  - **Warm start**: <100ms (VM from pool)
135
185
  - **Cold start**: ~4 seconds (new VM boot)
136
186
  - **Script overhead**: ~50ms
187
+ - **Workspace sync**: Every 30 seconds
137
188
 
138
189
  ## License
139
190
 
package/bin/mags.js CHANGED
@@ -14,6 +14,10 @@ const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.mags
14
14
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
15
15
  const SSH_KEY_FILE = path.join(CONFIG_DIR, 'ssh_key');
16
16
 
17
+ // Claude skill paths
18
+ const CLAUDE_GLOBAL_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.claude', 'commands');
19
+ const SKILL_URL = 'https://mags.run/mags.md';
20
+
17
21
  // Load saved config
18
22
  function loadConfig() {
19
23
  try {
@@ -182,6 +186,7 @@ ${colors.bold}Commands:${colors.reset}
182
186
  list List recent jobs
183
187
  url <name|id> [port] Enable URL access for a job
184
188
  stop <name|id> Stop a running job
189
+ setup-claude Install Mags skill for Claude Code
185
190
 
186
191
  ${colors.bold}Run Options:${colors.reset}
187
192
  -w, --workspace <id> Use persistent workspace (S3 sync)
@@ -202,6 +207,7 @@ ${colors.bold}Examples:${colors.reset}
202
207
  mags status myvm
203
208
  mags logs myvm
204
209
  mags url myvm 8080
210
+ mags setup-claude # Install Claude Code skill
205
211
  `);
206
212
  process.exit(1);
207
213
  }
@@ -213,10 +219,10 @@ ${colors.cyan}${colors.bold}Mags Login${colors.reset}
213
219
  To authenticate, you need an API token from Magpie.
214
220
  `);
215
221
 
216
- log('blue', 'Opening Magpie dashboard to create an API token...');
222
+ log('blue', 'Opening Mags to create an API token...');
217
223
  console.log('');
218
224
 
219
- const tokenUrl = 'https://api.magpiecloud.com/api-keys';
225
+ const tokenUrl = 'https://mags.run/tokens';
220
226
  openBrowser(tokenUrl);
221
227
 
222
228
  await sleep(1000);
@@ -586,6 +592,95 @@ async function stopJob(nameOrId) {
586
592
  }
587
593
  }
588
594
 
595
+ // Download a file from URL
596
+ function downloadFile(url) {
597
+ return new Promise((resolve, reject) => {
598
+ const urlObj = new URL(url);
599
+ const lib = urlObj.protocol === 'https:' ? https : http;
600
+
601
+ lib.get(url, (res) => {
602
+ // Handle redirects
603
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
604
+ downloadFile(res.headers.location).then(resolve).catch(reject);
605
+ return;
606
+ }
607
+
608
+ if (res.statusCode !== 200) {
609
+ reject(new Error(`Failed to download: HTTP ${res.statusCode}`));
610
+ return;
611
+ }
612
+
613
+ let data = '';
614
+ res.on('data', chunk => data += chunk);
615
+ res.on('end', () => resolve(data));
616
+ }).on('error', reject);
617
+ });
618
+ }
619
+
620
+ async function setupClaude() {
621
+ console.log(`
622
+ ${colors.cyan}${colors.bold}Mags Claude Code Skill Setup${colors.reset}
623
+
624
+ This will install the Mags skill for Claude Code, allowing you to run
625
+ scripts on instant VMs directly from Claude.
626
+ `);
627
+
628
+ // Ask where to install
629
+ console.log(`${colors.bold}Installation options:${colors.reset}`);
630
+ console.log(` 1. Global (all projects) - ~/.claude/commands/`);
631
+ console.log(` 2. Current project only - ./.claude/commands/`);
632
+ console.log('');
633
+
634
+ const choice = await prompt(`${colors.bold}Choose installation type [1/2]: ${colors.reset}`);
635
+
636
+ let targetDir;
637
+ if (choice === '2') {
638
+ targetDir = path.join(process.cwd(), '.claude', 'commands');
639
+ log('blue', 'Installing to current project...');
640
+ } else {
641
+ targetDir = CLAUDE_GLOBAL_DIR;
642
+ log('blue', 'Installing globally...');
643
+ }
644
+
645
+ // Create directory if it doesn't exist
646
+ try {
647
+ if (!fs.existsSync(targetDir)) {
648
+ fs.mkdirSync(targetDir, { recursive: true });
649
+ }
650
+ } catch (err) {
651
+ log('red', `Failed to create directory: ${err.message}`);
652
+ process.exit(1);
653
+ }
654
+
655
+ // Download skill file
656
+ log('blue', `Downloading skill from ${SKILL_URL}...`);
657
+
658
+ try {
659
+ const skillContent = await downloadFile(SKILL_URL);
660
+
661
+ const targetFile = path.join(targetDir, 'mags.md');
662
+ fs.writeFileSync(targetFile, skillContent);
663
+
664
+ console.log('');
665
+ log('green', 'Mags skill installed successfully!');
666
+ log('gray', `Location: ${targetFile}`);
667
+ console.log('');
668
+ log('cyan', 'Next steps:');
669
+ console.log(` 1. Make sure you're logged in: ${colors.bold}mags login${colors.reset}`);
670
+ console.log(` 2. In Claude Code, use: ${colors.bold}/mags <your request>${colors.reset}`);
671
+ console.log('');
672
+ log('cyan', 'Example prompts:');
673
+ console.log(' /mags run echo Hello World');
674
+ console.log(' /mags create a python environment with numpy');
675
+ console.log(' /mags run a flask server and give me the URL');
676
+
677
+ } catch (err) {
678
+ log('red', `Failed to download skill: ${err.message}`);
679
+ log('gray', `You can manually download from: ${SKILL_URL}`);
680
+ process.exit(1);
681
+ }
682
+ }
683
+
589
684
  async function sshToJob(nameOrId) {
590
685
  if (!nameOrId) {
591
686
  log('red', 'Error: Job name or ID required');
@@ -643,7 +738,7 @@ async function sshToJob(nameOrId) {
643
738
 
644
739
  // Build SSH arguments
645
740
  const sshArgs = [
646
- '-t', // Force TTY allocation for interactive session
741
+ '-tt', // Force TTY allocation even when stdin isn't a terminal
647
742
  '-o', 'StrictHostKeyChecking=no',
648
743
  '-o', 'UserKnownHostsFile=/dev/null',
649
744
  '-o', 'LogLevel=ERROR',
@@ -716,7 +811,7 @@ async function main() {
716
811
  break;
717
812
  case '--version':
718
813
  case '-v':
719
- console.log('mags v1.3.0');
814
+ console.log('mags v1.4.1');
720
815
  process.exit(0);
721
816
  break;
722
817
  case 'new':
@@ -751,6 +846,9 @@ async function main() {
751
846
  await requireAuth();
752
847
  await stopJob(args[1]);
753
848
  break;
849
+ case 'setup-claude':
850
+ await setupClaude();
851
+ break;
754
852
  default:
755
853
  if (!command) {
756
854
  // No command - check if logged in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpiecloud/mags",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "description": "Mags CLI - Execute scripts on Magpie's instant VM infrastructure",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -17,7 +17,9 @@
17
17
  "cloud",
18
18
  "serverless",
19
19
  "execution",
20
- "cli"
20
+ "cli",
21
+ "claude",
22
+ "claude-code"
21
23
  ],
22
24
  "author": "Magpie Cloud",
23
25
  "license": "MIT",
@@ -25,7 +27,7 @@
25
27
  "type": "git",
26
28
  "url": "https://github.com/magpiecloud/mags"
27
29
  },
28
- "homepage": "https://magpiecloud.com/docs/mags",
30
+ "homepage": "https://mags.run",
29
31
  "bugs": {
30
32
  "url": "https://github.com/magpiecloud/mags/issues"
31
33
  },