npm package: @magpiecloud/mags
Description: Mags CLI & SDK - Execute commands and scripts on Mags Sandboxes
Author: Magpie Cloud
Latest version: 1.9.0

# Mags - Instant VM Execution

Execute scripts instantly on Magpie's microVM infrastructure. VMs boot in <100ms from a warm pool, giving you instant access to isolated compute environments.

## What is Mags?

Mags is a CLI and SDK for running scripts on ephemeral microVMs. Each execution gets its own isolated VM that:

- Boots in ~300ms (from warm pool)
- Supports optional S3-backed persistent workspaces (with `-p` flag)
- Syncs /root directory automatically when persistence is enabled
- Can expose public URLs for web services
- Provides SSH access through secure proxy
- Auto-sleeps when idle and wakes on request

## Installation

```bash
npm install -g @magpiecloud/mags
```

## Authentication

### Interactive Login (Recommended)

```bash
mags login
```

This opens your browser to the Magpie dashboard where you can create an API token. Paste the token when prompted, and it will be saved to `~/.mags/config.json` for all future commands.

### Auth Commands

```bash
mags login     # Authenticate with Magpie
mags whoami    # Check current authentication status
mags logout    # Remove saved credentials
```

### Environment Variable (Alternative)

```bash
export MAGS_API_TOKEN="your-token-here"
```

## Quick Start

### Run a simple command

```bash
mags run 'echo Hello World'
```

### Create a sandbox

```bash
# Create a new VM (local disk only)
mags new myproject

# Create with S3 data persistence
mags new myproject -p

# SSH into it
mags ssh myproject
```

### Run with a persistent workspace

Workspaces persist data between runs using S3 sync:

```bash
# First run - install dependencies
mags run -w myproject 'apk add python3 py3-pip && pip install requests'

# Second run - dependencies are still there
mags run -w myproject 'python3 -c "import requests; print(requests.__version__)"'
```

### Deploy a web service with public URL

```bash
mags run -p --url 'python3 -m http.server 8080'
# Returns: https://abc123.apps.magpiecloud.com
```

## CLI Reference

### Commands

| Command | Description |
|---------|-------------|
| `mags login` | Authenticate with Magpie (saves token locally) |
| `mags logout` | Remove saved credentials |
| `mags whoami` | Show current authentication status |
| `mags new <workspace> [-p]` | Create a VM sandbox (add `-p` for S3 persistence) |
| `mags run [options] <script>` | Execute a script on a microVM |
| `mags ssh <workspace>` | Open interactive SSH session to a VM |
| `mags status <job-id>` | Get job status |
| `mags logs <job-id>` | Get job logs |
| `mags list` | List recent jobs |
| `mags url <job-id> [port]` | Enable URL access for a job |
| `mags stop <job-id>` | Stop a running job |
| `mags set <name\|id> [options]` | Update VM settings (`--no-sleep`, `--sleep`) |

### Run Options

| Flag | Description | Default |
|------|-------------|---------|
| `-w, --workspace <id>` | Use persistent workspace (S3 sync) | auto-generated |
| `-p, --persistent` | Keep VM alive after script completes | false |
| `--url` | Enable public URL access (requires -p) | false |
| `--port <port>` | Port to expose for URL access | 8080 |
| `--no-sleep` | Keep VM always running, never auto-sleep (requires -p) | false |
| `--startup-command <cmd>` | Command to run when VM wakes from sleep | none |

## SSH Access

Connect to any running VM via SSH:

```bash
mags ssh myproject
```

### SSH Features

- **Secure proxy**: Connect via `api.magpiecloud.com:PORT` (agent IPs are hidden)
- **PTY support**: Full interactive terminal with colors and editors
- **Key-based auth**: Automatic SSH key management
- **Hostname**: VMs use `mags-vm` as hostname

### SSH Session Example

```bash
$ mags ssh myproject
Connecting to api.magpiecloud.com:40006...
(Use Ctrl+D or 'exit' to disconnect)

mags-vm:~# ls
index.html  test.txt

mags-vm:~# python3 --version
Python 3.11.6

mags-vm:~# exit
```

## Workspaces & Persistence

### How It Works

When you use a workspace, Mags:

1. Mounts a JuiceFS filesystem backed by S3
2. Creates an OverlayFS to capture all filesystem changes
3. Syncs your `/root` directory to S3 every 30 seconds
4. Restores your files when you reconnect

### What Gets Persisted

- `/root` - Your home directory (synced every 30 seconds)
- `/jfs` - Direct JuiceFS mount (instant S3 sync)
- Installed packages and dependencies
- Configuration files and dotfiles

### Sync Behavior

| Event | Behavior |
|-------|----------|
| While running | Auto-sync every 30 seconds |
| On stop | Full sync before VM terminates |
| On wake | Previous state restored instantly |

## Usage Examples

### Basic Execution

```bash
# Simple command
mags run 'echo Hello World'

# Multi-line script
mags run 'apk add curl && curl -s https://api.github.com/users/octocat | head -5'

# With environment info
mags run 'uname -a && cat /etc/os-release'
```

### Persistent Workspaces

```bash
# Create a workspace with dependencies
mags run -w ml-project 'apk add python3 py3-pip && pip install numpy pandas scikit-learn'

# Run scripts using the workspace
mags run -w ml-project 'python3 train.py'

# All files in /root are persisted
mags run -w ml-project 'ls -la /root'
```

### Web Services

```bash
# Simple static server
mags run -p --url 'python3 -m http.server 8080'

# Node.js app
mags run -w myapp -p --url --port 3000 'npm install && npm start'

# Flask app
mags run -w flask-app -p --url 'pip install flask && python app.py'

# Custom startup command for auto-wake
mags run -w api -p --url --startup-command 'python server.py' 'pip install -r requirements.txt && python server.py'
```

### Always-On VMs

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:

```bash
# Always-on API server (never auto-sleeps)
mags run -w my-api -p --no-sleep --url --port 3000 'npm start'

# Always-on background worker
mags run -w worker -p --no-sleep 'python worker.py'
```

If an always-on VM's host becomes unhealthy, the orchestrator automatically re-provisions it on a healthy agent within ~60 seconds.

### Interactive Development

```bash
# Create a dev environment (local disk)
mags new dev-env

# SSH in and work
mags ssh dev-env

# Inside the VM:
mags-vm:~# apk add git nodejs npm
mags-vm:~# git clone https://github.com/user/repo
mags-vm:~# cd repo && npm install
mags-vm:~# npm run dev
```

## Node.js SDK

For programmatic access, use the SDK:

```javascript
const Mags = require('@magpiecloud/mags');

const mags = new Mags({
  apiToken: process.env.MAGS_API_TOKEN
});
```

### SDK Methods

#### Jobs

| Method | Description |
|--------|-------------|
| `run(script, options)` | Submit a job. Options: `name`, `workspaceId`, `baseWorkspaceId`, `persistent`, `noSleep`, `ephemeral`, `startupCommand`, `environment`, `fileIds`, `diskGb` |
| `runAndWait(script, options)` | Submit and block until done. Extra options: `timeout`, `pollInterval` |
| `new(name, options)` | Create a VM sandbox and wait until running. Options: `persistent`, `baseWorkspaceId`, `diskGb`, `timeout` |
| `status(requestId)` | Get job status |
| `logs(requestId)` | Get job logs |
| `list({page, pageSize})` | List recent jobs (paginated) |
| `findJob(nameOrId)` | Find job by name, workspace ID, or request ID |
| `updateJob(requestId, {startupCommand, noSleep})` | Update job settings |
| `stop(nameOrId)` | Stop a job (accepts name, workspace ID, or request ID) |
| `exec(nameOrId, command, {timeout})` | Execute a command on a running/sleeping VM via SSH |
| `sync(requestId)` | Sync workspace to S3 without stopping |
| `resize(workspace, diskGb)` | Resize workspace disk (stops VM, creates new one) |
| `usage({windowDays})` | Get aggregated usage summary |

#### URL & Access

| Method | Description |
|--------|-------------|
| `enableAccess(requestId, port)` | Enable SSH (port 22) or HTTP access (default 8080) |
| `url(nameOrId, port)` | Enable public URL and return the full URL |
| `urlAliasCreate(subdomain, workspaceId, domain)` | Create a stable URL alias for a workspace |
| `urlAliasList()` | List all URL aliases |
| `urlAliasDelete(subdomain)` | Delete a URL alias |

#### Workspaces

| Method | Description |
|--------|-------------|
| `listWorkspaces()` | List all workspaces |
| `deleteWorkspace(workspaceId)` | Delete a workspace and its S3 data |

#### Files

| Method | Description |
|--------|-------------|
| `uploadFiles(filePaths)` | Upload local files, returns array of file IDs |

#### Cron Jobs

| Method | Description |
|--------|-------------|
| `cronCreate({name, cronExpression, script, workspaceId, environment, persistent})` | Create a scheduled job |
| `cronList()` | List all cron jobs |
| `cronGet(id)` | Get a cron job |
| `cronUpdate(id, updates)` | Update a cron job |
| `cronDelete(id)` | Delete a cron job |

### SDK Examples

```javascript
// Run and wait for completion
const result = await mags.runAndWait('echo Hello World');
console.log(result.logs);

// Create a sandbox (local disk)
await mags.new('myproject');

// Create a sandbox with S3 persistence
await mags.new('myproject', { persistent: true });

// Execute command on existing VM
const { output } = await mags.exec('myproject', 'ls -la /root');
console.log(output);

// Run with workspace and options
const job = await mags.run('python script.py', {
  workspaceId: 'myproject',
  persistent: true,
  diskGb: 5,
  startupCommand: 'python server.py'
});

// Always-on VM (never auto-sleeps)
await mags.run('node server.js', {
  workspaceId: 'my-api',
  persistent: true,
  noSleep: true
});

// Enable public URL
const { url } = await mags.url('my-api', 3000);
console.log(url); // https://abc123.apps.magpiecloud.com

// Create stable URL alias
await mags.urlAliasCreate('my-app', 'my-api');

// Resize workspace disk
await mags.resize('myproject', 10); // 10GB

// Stop a job by name
await mags.stop('myproject');
```

## API Endpoints

For direct API access:

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/mags-jobs` | POST | Submit a new job |
| `/api/v1/mags-jobs` | GET | List jobs (paginated) |
| `/api/v1/mags-jobs/{id}/status` | GET | Get job status |
| `/api/v1/mags-jobs/{id}/logs` | GET | Get job logs |
| `/api/v1/mags-jobs/{id}/access` | POST | Enable SSH/URL access |
| `/api/v1/mags-jobs/{id}` | PATCH | Update job settings |

### Example API Request

```bash
curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
  -H "Authorization: Bearer $MAGS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "script": "echo Hello World",
    "type": "inline",
    "workspace_id": "myproject",
    "persistent": true,
    "no_sleep": true
  }'
```

## Job Status Values

| Status | Description |
|--------|-------------|
| `pending` | Job queued, waiting for VM |
| `running` | Script executing |
| `sleeping` | Persistent VM idle (wakes on request) |
| `completed` | Script finished successfully |
| `error` | Script failed |

## VM Environment

Each VM runs Alpine Linux with hostname `mags-vm` and includes:

- `/root` - Persistent home directory (synced to S3)
- `/jfs` - Direct JuiceFS mount
- Common tools: curl, wget, git, python3, nodejs
- Package manager: `apk add <package>`

### Installing Packages

```bash
# Python packages
mags run 'apk add python3 py3-pip && pip install requests flask pandas'

# Node.js packages
mags run 'apk add nodejs npm && npm install -g express'

# System packages
mags run 'apk add ffmpeg imagemagick'
```

## Performance

| Metric | Value |
|--------|-------|
| Warm start | ~300ms |
| Cold start | ~4 seconds |
| Script overhead | ~50ms |
| Workspace sync | Every 30 seconds |

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `MAGS_API_TOKEN` | Your API token (required) | - |
| `MAGS_API_URL` | API endpoint | https://api.magpiecloud.com |

## Troubleshooting

### "API token required"

Set your API token:
```bash
mags login
# or
export MAGS_API_TOKEN="your-token-here"
```

### "Job timed out"

Increase the timeout or check if your script is hanging:
```bash
mags run --timeout 600 'long-running-script.sh'
```

### "Workspace not found"

Workspace IDs are case-sensitive. Check with:
```bash
mags list
```

### URL not accessible

Make sure you're using both `-p` (persistent) and `--url` flags, and your app is listening on the correct port:
```bash
mags run -p --url --port 3000 'node server.js'
```

### SSH connection issues

SSH connects through our proxy. If you have issues:
1. Ensure the job is running: `mags status <id>`
2. Try reconnecting: `mags ssh <workspace>`

## Support

- Documentation: https://mags.run
- API Reference: https://mags.run/api.html
- Dashboard: https://mags.run/tokens.html

## License

MIT