Skip to content

sup.vm

The sup.vm() function launches dedicated Linux VMs for executing shell commands, running build processes, serving web content, and processing media files. Each VM provides full Linux environment access with persistent storage and network capabilities.


Creating a VM

sup.vm()

() → SupVM

// Create a new VM
const vm = sup.vm();
// Reuse VM across invocations by storing in global state
let vm = sup.global.get('vm');
if (!vm) {
vm = sup.vm();
sup.global.set('vm', vm);
}

Launches a fresh Linux VM. Each call provisions a new VM instance. To reuse the same VM across multiple patch invocations, persist the SupVM object in the datastore using sup.global.set().

In most patches, you should not call sup.vm() on every run.

  • Prefer one VM per patch (single shared VM)
  • Or one VM per chat context (keyed by sup.chat.id)
  • Recreate only when missing or unhealthy

Creating many short-lived VMs is usually slower than reusing an existing VM.

Pattern: one VM per patch

function ensurePatchVm() {
let vm = sup.global.get('vm');
if (vm) {
const health = vm.execResult('echo ok');
if (health.exitCode === 0) return vm;
}
vm = sup.vm();
vm.exec('mkdir -p /workspace/app'); // Optional one-time init
sup.global.set('vm', vm);
return vm;
}

Pattern: one VM per chat (keyed by chat ID)

function ensureChatVm() {
const key = `vm:${sup.chat.id}`;
let vm = sup.global.get(key);
if (vm) {
const health = vm.execResult('echo ok');
if (health.exitCode === 0) return vm;
}
vm = sup.vm();
vm.exec('mkdir -p /workspace/app'); // Optional one-time init
sup.global.set(key, vm);
return vm;
}

VM Properties

vm.id

string

const vmId = vm.id;
// 'vm-1234abcd'

Unique identifier for the VM instance. This ID remains stable across invocations if the VM is persisted in the datastore.

vm.url

string

vm.execStreaming('python3 -m http.server 3000');
const webUrl = vm.url;
// 'https://abcd.supvm.com'

Public HTTPS URL that forwards traffic to port 3000 on the VM. Use this to expose web servers, APIs, or any HTTP-accessible service running inside the VM.


Command Execution

vm.exec()

(command: string | string[], options?: { cwd?: string; env?: Record<string, string> }) → string

// Simple command
const output = vm.exec('uname -a');
// 'Linux supvm-1234 ...'
// With working directory
const files = vm.exec('ls', { cwd: 'app' });
// 'README.md\nindex.mjs\n'
// With environment variables
const result = vm.exec('echo $MY_VAR', {
env: { MY_VAR: 'hello' }
});
// 'hello\n'
// Array syntax for safer argument handling
const output = vm.exec(['git', 'clone', repoUrl]);

Runs a command and returns merged stdout/stderr output. Throws a SupVMExecError if the command exits with a non-zero code.

Command Format:

  • String: Executed via sh -c (supports shell features like pipes, redirects)
  • Array: Direct execution (safer for dynamic arguments, no shell injection risk)

Options:

  • cwd: Working directory for the command
  • env: Environment variables to set (merged with default environment)

Error Handling: Throws SupVMExecError with properties:

  • exitCode: Command exit code
  • stdout: Standard output
  • stderr: Standard error
  • output: Combined stdout/stderr
  • reason: Optional error description

vm.execResult()

(command: string | string[], options?: { cwd?: string; env?: Record<string, string> }) → { output: string; stdout: string; stderr: string; exitCode: number; error?: string }

const result = vm.execResult(['node', '--version']);
// { exitCode: 0, stdout: 'v18.17.0\n', stderr: '', output: 'v18.17.0\n' }
const result = vm.execResult('exit 1');
// { exitCode: 1, stdout: '', stderr: '', output: '', error: 'Command failed' }
if (result.exitCode !== 0) {
return `Build failed: ${result.output}`;
}

Runs a command and captures stdout/stderr/exitCode without throwing on failure. Useful when you need to handle errors programmatically or when non-zero exit codes are expected.

Returns:

  • exitCode: Command exit code (0 = success)
  • stdout: Standard output only
  • stderr: Standard error only
  • output: Combined stdout/stderr
  • error: Optional error message

vm.execStreaming()

(command: string | string[], options?: { cwd?: string; env?: Record<string, string> }) → SupVMStreamHandle

// Stream output for long-running commands
const cmd = vm.execStreaming('apt-get install -y neovim');
let collected = '';
for (const chunk of cmd.output()) {
collected += chunk;
sup.status(collected);
}
// Run in background and check later
const server = vm.execStreaming('python3 -m http.server 3000');
sup.sleep(1000);
console.log(server.status); // { state: 'running' }
// Stream stdout only
const build = vm.execStreaming('npm run build');
for (const chunk of build.stdout()) {
sup.status(`Building: ${chunk}`);
}

Executes a command and returns a handle for streaming output in real-time. Useful for long-running processes, build commands, or when you need to provide progress updates.

Stream Handle Methods: See SupVMStreamHandle section below.


File Transfer

vm.upload()

(source: string | SupImage | SupAudio | SupVideo | SupFile, dest?: string) → string

// Upload with automatic filename
const path = vm.upload(sup.input.image);
// 'image.png'
// Upload with custom destination
const path = vm.upload(sup.input.image, 'thumbnail.png');
// 'thumbnail.png'
// Upload text content
const path = vm.upload('console.log("Hello")', 'script.js');
// 'script.js'
// Process uploaded file
const imagePath = vm.upload(sup.input.image, 'input.png');
vm.exec(`convert ${imagePath} -resize 512x512 output.png`);

Copies content into the VM filesystem and returns the destination path.

Source Types:

  • string: URL or raw text content
  • SupImage, SupAudio, SupVideo, SupFile: Sup media objects

Destination:

  • If omitted, filename is inferred from content type using libmagic
  • If provided, must include filename (e.g., 'images/pic.png')

vm.uploadStreaming()

(source: string | SupImage | SupAudio | SupVideo | SupFile, dest?: string) → SupFileUploadHandle

// Upload with progress updates
const handle = vm.uploadStreaming(sup.input.file, 'dataset.zip');
for (const percent of handle.progress()) {
sup.status(`Uploading dataset.zip (${percent}%)`);
}
const path = handle.wait();
// 'dataset.zip'
// Upload in background
const upload = vm.uploadStreaming(largeFile, 'data.bin');
// Do other work...
const finalPath = upload.wait();

Uploads content with streaming progress updates. Useful for large files where you want to show upload progress.

Returns: SupFileUploadHandle for monitoring upload progress.

vm.download()

(path: string) → SupImage | SupAudio | SupVideo | SupFile

// Download and return as Sup media
const image = vm.download('output.png');
return image;
// Download after processing
vm.exec('convert input.png -blur 0x8 blurred.png');
const result = vm.download('blurred.png');
return result;

Downloads a file from the VM and returns it as a Sup media object. The return type is automatically determined by the file’s MIME type.

Returns:

  • SupImage for image files (PNG, JPG, GIF, etc.)
  • SupAudio for audio files (MP3, WAV, etc.)
  • SupVideo for video files (MP4, WebM, etc.)
  • SupFile for other file types

vm.downloadStreaming()

(path: string) → SupFileDownloadHandle

// Download with progress
const handle = vm.downloadStreaming('large-file.iso');
for (const percent of handle.progress()) {
sup.status(`Downloading (${percent}%)`);
}
const file = handle.wait();
return file;
// Download in background
const download = vm.downloadStreaming('output.mp4');
// Do other work...
const video = download.result;

Downloads a file with streaming progress updates. Useful for large files where you want to show download progress.

Returns: SupFileDownloadHandle for monitoring download progress.

vm.cat()

(path: string) → string

// Read text file contents
const config = vm.cat('/workspace/.env');
console.log(config);
// Read command output
vm.exec('echo "Hello, World!" > output.txt');
const content = vm.cat('output.txt');
// 'Hello, World!\n'

Reads a text file from the VM and returns its contents as a string. Convenience method for reading text files without needing to download and parse them.


Session Management

vm.session()

() → SupVMSession

// Create session with persistent context
const session = vm.session();
session.cd('/app');
session.setEnv('NODE_ENV', 'production');
session.exec('npm install');
session.exec('npm run build');
// Persist session across invocations
sup.global.set('vm-session', session);
const session = sup.global.get('vm-session');

Creates a session object that maintains working directory and environment variables across multiple commands. This is cleaner than passing cwd and env options to every exec() call.

Session Methods: See SupVMSession section below.

vm.delete()

() → void

// Clean up VM when done
vm.delete();
sup.global.set('vm', undefined);
// Conditional cleanup
if (sup.input.text === 'cleanup') {
const vm = sup.global.get('vm');
if (vm) {
vm.delete();
sup.global.set('vm', undefined);
return 'VM deleted';
}
}

Immediately tears down the VM and releases all resources. Use this when you’re done with a VM to avoid leaving it running.

Important: After calling delete(), the VM object becomes invalid. Remove it from the datastore to avoid attempting to use a deleted VM.


SupVMSession

A helper class that maintains working directory and environment state across multiple VM commands.

session.cd()

(dir: string) → void

session.cd('/app');
session.exec('ls'); // Lists /app contents

Sets the working directory for all subsequent commands in this session.

session.setEnv()

(key: string, value: string) → void

session.setEnv('NODE_ENV', 'production');
session.setEnv('PORT', '3000');
session.exec('npm start');

Sets an environment variable for all subsequent commands in this session.

session.cwd

string | undefined

console.log(session.cwd);
// '/workspace/app/api'

Current working directory override for this session, if any.

session.env

Record<string, string>

console.log(session.env);
// { NODE_ENV: 'production', PORT: '3000' }

Snapshot of environment variable overrides applied to this session.

session.exec()

(command: string | string[]) → string

const output = session.exec('npm test');

Runs a command with the session’s cwd and env settings. Throws on non-zero exit code.

session.execResult()

(command: string | string[]) → { output: string; stdout: string; stderr: string; exitCode: number; error?: string }

const result = session.execResult('npm test');
if (result.exitCode !== 0) {
return `Tests failed: ${result.output}`;
}

Runs a command with the session’s context and returns result without throwing.

session.execStreaming()

(command: string | string[]) → SupVMStreamHandle

const stream = session.execStreaming('npm run dev');
for (const chunk of stream.output()) {
sup.status(chunk);
}

Streams command output with the session’s context applied.


SupVMStreamHandle

Handle returned by vm.execStreaming() for controlling and reading from streaming command execution.

handle.output()

() → Generator<string>

let collected = '';
for (const chunk of handle.output()) {
collected += chunk;
sup.status(collected);
}
return collected;

Iterates over merged stdout+stderr chunks as they arrive.

handle.stdout()

() → Generator<string>

let output = '';
for (const chunk of handle.stdout()) {
output += chunk;
sup.status(`Building: ${output}`);
}
return output;

Iterates over stdout chunks only, ignoring stderr.

handle.stderr()

() → Generator<string>

let errors = '';
for (const chunk of handle.stderr()) {
errors += chunk;
sup.status(`Errors: ${errors}`);
}
return errors;

Iterates over stderr chunks only, ignoring stdout.

handle.sendStdin()

(text: string) → void

const handle = vm.execStreaming('python3');
handle.sendStdin('print(1 + 1)\n');
handle.sendEOF();
const output = handle.block();
// '2\n'

Writes text to the running process’s stdin. Useful for interactive commands that read from stdin.

handle.sendEOF()

() → void

const handle = vm.execStreaming('python3');
handle.sendStdin('1 + 1\n');
handle.sendEOF(); // Signal end of input
const result = handle.block();

Closes stdin (sends EOF signal). Required to signal the end of input for commands that read from stdin.

handle.kill()

() → void

const server = vm.execStreaming('python3 -m http.server');
sup.sleep(5000);
server.kill(); // Terminate after 5 seconds

Terminates the running process immediately. First attempts SIGTERM, then SIGKILL after a timeout if the process is still alive.

handle.block()

() → string

const handle = vm.execStreaming('echo done');
const output = handle.block();
// 'done\n'

Waits for the command to exit and returns merged output. Throws SupVMExecError if the exit code is non-zero.

handle.blockResult()

() → { output: string; stdout: string; stderr: string; exitCode: number; error?: string }

const { exitCode, output } = handle.blockResult();
if (exitCode !== 0) {
return `Build failed: ${output}`;
}
return `Success: ${output}`;

Waits for exit and captures stdout/stderr/exitCode without throwing. Useful when non-zero exit codes are expected.

handle.status

{ state: 'running' | 'exited' | 'killed' | 'error'; exitCode?: number }

if (handle.status.state === 'exited') {
return `Job completed with code ${handle.status.exitCode}`;
}
// Check periodically
while (handle.status.state === 'running') {
sup.status('Still running...');
sup.sleep(1000);
}

Lightweight status check without blocking. Check this property to determine if a command is still running.

handle.tail()

(n: number) → string[]

const recentOutput = handle.tail(20).join('\n');
sup.status(recentOutput);

Fetches the last N lines of merged output (best effort). Useful for checking recent output without reading the entire stream.


SupFileUploadHandle

Handle returned by vm.uploadStreaming() for monitoring file upload progress.

handle.progress()

() → Generator<number>

for (const percent of handle.progress()) {
sup.status(`Uploading: ${percent}%`);
}

Iterates over completion percentages from 0 to 100 as the upload progresses.

handle.wait()

() → string

const path = handle.wait();
// 'uploaded-file.zip'

Blocks until upload completes and returns the final file path in the VM.

handle.path

string

handle.wait();
const filePath = handle.path;
// 'uploaded-file.zip'

Final path once the upload finishes. Only valid after calling wait() or draining the progress iterator.


SupFileDownloadHandle

Handle returned by vm.downloadStreaming() for monitoring file download progress.

handle.progress()

() → Generator<number>

for (const percent of handle.progress()) {
sup.status(`Downloading: ${percent}%`);
}

Iterates over completion percentages from 0 to 100 as the download progresses.

handle.wait()

() → SupImage | SupAudio | SupVideo | SupFile

const file = handle.wait();
return file;

Blocks until download completes and returns the downloaded media object.

handle.result

SupImage | SupAudio | SupVideo | SupFile

handle.wait();
const media = handle.result;
return media;

Final downloaded media object. Only valid after calling wait() or draining the progress iterator.


Error Handling

SupVMExecError

Exception thrown when a VM command exits with a non-zero code.

try {
vm.exec('exit 1');
} catch (error) {
console.log(error.exitCode); // 1
console.log(error.stdout); // ''
console.log(error.stderr); // ''
console.log(error.output); // Combined output
console.log(error.reason); // Optional error description
}

Properties:

  • exitCode: Command exit code (non-zero)
  • stdout: Standard output
  • stderr: Standard error
  • output: Combined stdout/stderr
  • reason: Optional error description
  • message: Error message string

Common Patterns

function ensureVm() {
const key = `vm:${sup.chat.id}`; // Use 'vm' for one VM per patch
let vm = sup.global.get(key);
if (vm) {
const health = vm.execResult('echo ok');
if (health.exitCode === 0) return vm;
}
vm = sup.vm();
vm.exec('mkdir -p /workspace/app');
vm.execResult('node --version'); // Optional warm-up check
sup.global.set(key, vm);
return vm;
}
function main() {
const vm = ensureVm();
return `Ready: ${vm.id}`;
}

Web Server

function main() {
const key = `vm:${sup.chat.id}`;
let vm = sup.global.get(key);
if (!vm || vm.execResult('echo ok').exitCode !== 0) {
vm = sup.vm();
sup.global.set(key, vm);
}
// Start server only if not already running
const check = vm.execResult('pgrep -f "python3 -m http.server 3000"');
if (check.exitCode !== 0) {
vm.execStreaming('python3 -m http.server 3000');
}
return [
'Server is running at:',
vm.url
];
}

Image Processing

function main() {
// Reuse per-chat VM by default
const key = `vm:${sup.chat.id}`;
let vm = sup.global.get(key);
if (!vm || vm.execResult('echo ok').exitCode !== 0) {
vm = sup.vm();
sup.global.set(key, vm);
}
// Upload image
const inputPath = vm.upload(sup.input.image, 'input.png');
// Process with ImageMagick
vm.exec(`convert ${inputPath} -resize 512x512 -blur 0x8 output.png`);
// Download result
const result = vm.download('output.png');
return result;
}

Ephemeral VM job (exception)

Use this only for truly one-off tasks where cleanup is required immediately after execution.

function main() {
const vm = sup.vm();
try {
vm.exec('echo "one-off task" > /workspace/job.txt');
return vm.cat('/workspace/job.txt');
} finally {
vm.delete();
}
}