Compile
From script to native executable — one click. The receiver installs nothing.
This is the thing that sets OpticScript apart: every script you
write in the Playground is one button away from a real, self-contained
.exe (or Linux / macOS binary). No Python runtime, no Node install,
no pip install, no Docker image, no node_modules. One file. Ship
it. Done.
$ ls -lh bin/
-rwxr-xr-x 1 mlc staff 16M May 17 12:33 enhance
$ ./enhance --input portrait.jpg --output portrait_warm.jpg \
--brightness 1.15 --saturation 1.4 --vignette 0.5
✓ portrait_warm.jpg
That binary is ~16 MB, embeds the whole image engine plus your script, and runs at the same native speed as the Playground itself.
In the Playground — one click
Click the Make Exe button in the Editor toolbar. A dialog opens, asks for a tool name, picks your current platform by default, and a moment later hands you a path to the finished binary. The build takes milliseconds, not minutes — the engine ships as a pre-built stub and the compiler just appends your script as a blob to the tail.
The same workflow on the command line:
mlcos-compile-rs enhance.js
# → ./enhance (Linux/macOS)
# → ./enhance.exe (Windows)
Run it anywhere — the recipient does not need OpticScript, the playground, Rust, Go, Python, or any runtime. The binary is fully self-contained.
What ends up in the binary
| Embedded | Why it matters |
|---|---|
| The image engine (~16 MB) | Native pixel ops, no external runtime. |
| Your script | As JS source — --help extracts it back so future-you knows what the tool does. |
| Both built-in fonts (Inter + JetBrains Mono) | Text overlays / watermarks need no font files. |
| Installed engine extensions | Custom JS modules (e.g. the cube extension) ride along automatically. |
What's not embedded (kept external on purpose):
- AI model weights (GFPGAN, Real-ESRGAN). Tools that call those run the binary alongside the AI Tools Pack — same model files as the Playground uses.
Your script's directives become CLI flags
Every //!INPUT:, //!OUTPUT:, and //!PARAM: line at the top of
the script turns into a typed command-line flag. The script:
// enhance.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: BRIGHTNESS:number=1.10,min=0.5,max=2.0
//!PARAM: SATURATION:number=1.20,min=0,max=3
//!PARAM: VIGNETTE:number=0.30,min=0,max=1
Engine.loadImage(INPUT)
.brightness(BRIGHTNESS)
.saturation(SATURATION)
.vignette(VIGNETTE)
.save(OUTPUT);
Compiled and asked for help:
$ ./enhance --help
enhance
Created with MLC OpticScript v2.1.3 · https://mlcgo.eu
Copyright (c) 2026 Michael Lechner · Elastic License 2.0
USAGE:
enhance --INPUT=<path> --OUTPUT=<path> --BRIGHTNESS=<value>
--SATURATION=<value> --VIGNETTE=<value>
INPUTS:
--INPUT=<path> (load image)
OUTPUTS:
--OUTPUT=<path> (save target; `-` for stdout)
PARAMETERS:
--BRIGHTNESS=<value> (default: 1.10)
--SATURATION=<value> (default: 1.20)
--VIGNETTE=<value> (default: 0.30)
The min/max ranges declared in //!PARAM: are enforced at runtime —
out-of-range values are rejected before the script even starts.
Batch a whole folder — no script change
The same binary handles batch processing. Point --INPUT at a glob
and --OUTPUT at a template, and the engine stays "warm" across the
loop:
./enhance --INPUT='photos/*.jpg' --OUTPUT='enhanced/{stem}.png'
batch: 17 file(s) via --INPUT
✓ OUTPUT → enhanced/IMG_0001.png
✓ OUTPUT → enhanced/IMG_0002.png
…
| Input pattern | Example |
|---|---|
| Glob | 'photos/*.jpg', '**/*.png' |
| File list (one per line) | @todo.txt (# comments allowed) |
| Comma list | a.png,b.png,c.png |
Output templates support {stem} / {name} / {ext} / {dir} /
{idx} / {n} placeholders.
Filter mode — - is stdin / stdout
Every compiled binary doubles as a Unix-style filter:
# Read PNG from stdin, write JPEG to stdout
cat photo.png | ./enhance --INPUT=- --OUTPUT=- \
--stdout-format=jpg --silent > out.jpg
# Chain two filters
cat in.jpg | ./grayscale --INPUT=- --OUTPUT=- --silent \
| ./watermark --INPUT=- --OUTPUT=- --silent \
--BRAND="© Acme" > final.png
Feature checklist
Things that "just work" inside every compiled binary, with no external dependencies:
- PNG / JPEG / WebP / AVIF / TIFF read + write
- Animated WebP + APNG + multi-page TIFF for animations
- EXIF read on every load, write on JPEG save (
meta.exif.*) - SVG import (resvg) and SVG export (vtracer) — full vector loop
- 35+ pixel operations — blur, sharpen, warp, blend, color grading, edge detection, …
- Text rendering with two embedded variable fonts (Inter, JetBrains Mono)
- WarpGrid mesh transforms for perspective / 3D-surface projection
- Custom convolution kernels for sharpen / emboss / motion-blur / etc.
Cross-platform builds
The Playground's "Make Exe" button defaults to your current OS.
Cross-compilation (build a Windows .exe from macOS, for example)
is a Pro feature — see the pricing page.
On the command line:
# Native (host platform)
mlcos-compile-rs enhance.js --overwrite
# Override the output name / path
mlcos-compile-rs enhance.js --name colour-enhancer --output ~/bin/
# Cross-target (Pro): point at a pre-downloaded stub for the target OS
mlcos-compile-rs enhance.js --stub ./stubs/mlcos-run-rs-windows-x64.exe
Three ways to distribute, in order of effort
You don't always need a full compile. The mlcos installer is small; when your audience already has it, lighter options work too:
| Audience | What to ship | What they install |
|---|---|---|
| Your team / internal | enhance.js + mlcos-run-rs enhance.js … |
OpticScript |
| Polished tool, internal | mlcos-compile-rs --wrap=auto enhance.js → enhance.cmd / enhance.sh |
OpticScript |
| External user | enhance.exe (compiled) |
Nothing |
Compiled binaries are the answer for the third row — the one where you want zero questions from the recipient.
Troubleshooting
INPUT is not definedat runtime — the script usesINPUTbut doesn't declare//!INPUT: INPUT(or the relevant key). Add the directive so the compiler knows what to wire up.batch mode … requires a placeholder— your input matched multiple files but the output is a fixed path. Add{stem}(or another placeholder) to the output.two inputs are multi-valued patterns; only one input may batch— fix one input to a single file; the other can batch.
See also
- first_steps_js.md — JS engine tour.
- api-reference — every
Engine.*method. - examples — 70+ working scripts. Open in the Playground, compile, ship.