Your own programs

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

Windows: icon and file properties

A compiled .exe carries the OpticScript icon and fills in Properties → Details: product name and description come from the script's //!TITLE: and //!DESC:, the version defaults to 1.0.0. Your own:

mlcos-compile-rs enhance.js --icon logo.png --app-version 1.2.0 \
    --company "Example Ltd" --copyright "© 2026 Example Ltd"

--icon takes an .ico or any PNG/JPEG/WebP — the sizes Windows needs are generated. This works when compiling for Windows from Linux or macOS too (--stub …/mlcos-run-rs.exe).

macOS: fine locally, not for handing on

On macOS the binaries you build run perfectly on your own machine — but you cannot pass them to anyone else.

The compiler appends the script past the end of the stub. The kernel never notices, because it only validates up to the signature's codeLimit. codesign very much does:

codesign --verify ./enhance   → main executable failed strict validation
spctl --assess    ./enhance   → rejected
codesign --sign - ./enhance   → refuses to sign it at all

So the file can be neither signed nor notarised. As long as it stays on your machine nobody notices: locally built files carry no quarantine attribute and Gatekeeper never looks at them. The moment someone downloads it or receives it over AirDrop, Gatekeeper rejects it — with no way around it on their side.

To hand something to macOS users, pick one of the lighter routes in the next section (a wrapper script, or the plain .js), or ship the script and let the recipient run mlcos-compile-rs themselves. Windows and Linux are unaffected.

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 defined at runtime — the script uses INPUT but 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