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 VMconst vm = sup.vm();
// Reuse VM across invocations by storing in global statelet 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().
Recommended lifecycle and reuse strategy
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 commandconst output = vm.exec('uname -a');// 'Linux supvm-1234 ...'
// With working directoryconst files = vm.exec('ls', { cwd: 'app' });// 'README.md\nindex.mjs\n'
// With environment variablesconst result = vm.exec('echo $MY_VAR', { env: { MY_VAR: 'hello' }});// 'hello\n'
// Array syntax for safer argument handlingconst 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 commandenv: Environment variables to set (merged with default environment)
Error Handling:
Throws SupVMExecError with properties:
exitCode: Command exit codestdout: Standard outputstderr: Standard erroroutput: Combined stdout/stderrreason: 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 onlystderr: Standard error onlyoutput: Combined stdout/stderrerror: Optional error message
vm.execStreaming()
(command: string | string[], options?: { cwd?: string; env?: Record<string, string> }) → SupVMStreamHandle
// Stream output for long-running commandsconst 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 laterconst server = vm.execStreaming('python3 -m http.server 3000');sup.sleep(1000);console.log(server.status); // { state: 'running' }
// Stream stdout onlyconst 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 filenameconst path = vm.upload(sup.input.image);// 'image.png'
// Upload with custom destinationconst path = vm.upload(sup.input.image, 'thumbnail.png');// 'thumbnail.png'
// Upload text contentconst path = vm.upload('console.log("Hello")', 'script.js');// 'script.js'
// Process uploaded fileconst 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 contentSupImage,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 updatesconst 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 backgroundconst 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 mediaconst image = vm.download('output.png');return image;
// Download after processingvm.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:
SupImagefor image files (PNG, JPG, GIF, etc.)SupAudiofor audio files (MP3, WAV, etc.)SupVideofor video files (MP4, WebM, etc.)SupFilefor other file types
vm.downloadStreaming()
(path: string) → SupFileDownloadHandle
// Download with progressconst handle = vm.downloadStreaming('large-file.iso');for (const percent of handle.progress()) { sup.status(`Downloading (${percent}%)`);}const file = handle.wait();return file;
// Download in backgroundconst 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 contentsconst config = vm.cat('/workspace/.env');console.log(config);
// Read command outputvm.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 contextconst session = vm.session();session.cd('/app');session.setEnv('NODE_ENV', 'production');session.exec('npm install');session.exec('npm run build');
// Persist session across invocationssup.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 donevm.delete();sup.global.set('vm', undefined);
// Conditional cleanupif (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 contentsSets 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 inputconst 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 secondsTerminates 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 periodicallywhile (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 outputstderr: Standard erroroutput: Combined stdout/stderrreason: Optional error descriptionmessage: Error message string
Common Patterns
Ensure VM (recommended default)
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(); }}