Wren
was intended to be small and embedded. Wrenlift flips that:
it runs your .wren files directly as standalone, portable scripts
and apps. Zero runtime deps!.
standalone · no host requiredtiered JITzero runtime deps
✦
✦
✦
~/projects/api
$ wlift run server.wren
no host needed · standalone
✓ listening on :8080 >
Quick setup.
One curl gives you wlift (the runtime), hatch (the package + build tool), and wlift-lsp (the language server), pulled from the latest GitHub Release, SHA256-verified, and dropped into ~/.local/bin.
Run the script. The default install dir is ~/.local/bin; override with INSTALL_DIR.
Open a new shell so wlift and hatch land on your PATH.
Run wlift hello.wren. That's it!
# install (macos · linux)$ curl -fsSL wrenlift.com/install.sh | sh
# verify$ wlift --version
wrenlift 0.1.16 (release, jit)# run a script$ wlift hello.wren
hello, world
macOS, building from source:cargo install strips
binaries, which invalidates the linker-generated adhoc
codesignature so macOS SIGKILLs them on spawn. Run
codesign --force --sign - ~/.cargo/bin/{wlift,hatch,wlift-lsp}
once after install.
targets
Runs everywhere.
Same source, multiple ABIs. The native build cross-compiles to Linux, Windows, and macOS; the wasm build runs interpreted or tier-up JIT'd in any modern browser. wlift --target wasm swaps the backend.
Desktop
native runtime · tier-up cranelift JIT · AOT to a single binary
Linuxx86_64, aarch64
macOS Intel + Apple Silicon
Windowsx86_64
AOTwlift --aot — ship a self-contained binary, no runtime install
Web
webassembly · tier-up wasm JIT
WebAssemblywasm32-unknown
Browser main + worker, WebGPU canvas
Async bridgesfetch, setTimeout, WebSocket
packages & libraries
Pair with Hatch for libraries.
Wrenlift powers Hatch, and Hatch brings the ecosystem. Add a package, lock the version, and hatch run takes care of the rest. Same runtime under the hood.
# add a hatch package$ hatch add @hatch:http resolved 3 packages# run with the hatch cli — it wraps wlift$ hatch run path/to/myApp
loading @hatch:http@0.3.0 loading @hatch:json@0.1.0 > standalone · serving :8080
diagnostics
Errors that read like prose.
When something goes wrong, Wrenlift doesn't just say "method not found." It points at the exact identifier, names the type involved, and tells you what to try next when it can.
1Located. File, line, and column.
2Source-anchored. Caret on the failing identifier.
3Class-aware. Names the type, not just the symbol.
4Actionable. Suggests the fix when it can.
counter.wrenRUNTIME ERROR
Error: Counter does not implement 'tally'╭─[counter.wren:22:34]│22│System.print("Final count: " + c.tally.toString)
│─────┬───────│╰─────── error occurred here││Note:`Counter` has no getter or method `tally`.│Did you mean to call it with `()`?────╯
host embedding
Or embed it in your C host.
Drop-in for the upstream Wren C API. Same WrenVM, same WrenConfiguration. Grab the wrenlift.h shim, link libwrenlift.a, and your existing host keeps working.
✓API-compatible.wrenNewVM, wrenInterpret, wrenCall, foreign methods, slots: all of it.
✓One library, two modes. Same build powers wlift on the CLI and embeds inside your game, editor, or daemon.
✓Bring your own loader.WrenLoadModuleFn is honoured. Ship Hatch packages or your own modules.
Or embed it in your Rust host.
Wrenlift ships first-class Rust bindings. Reach for hatch_runner when you want the Hatch-aware runtime in two lines, or drop down to the raw capi externs for full VM control.
✓High level: hatch_runner. One HatchRunner::new(), then run_file(). Resolves Hatch packages automatically.
✓Low level: capi.extern "C" bindings to wrenNewVM, wrenInterpret, slots, foreign methods. unsafe when you need it.
✓Cargo-native. Add wren_lift to your Cargo.toml; the build links the static lib for you.
Or embed it in the browser.
One ES-module import, no build step, no toolchain. https://wrenlift.com/dist/wlift.js is the canonical CDN URL — call createWlift() and run Wren in the page or a worker. Same runtime, same diagnostics, async bridges already wired (fetch, setTimeout, WebSocket, DOM). An npm package with versioned tags is on the roadmap.
✓One factory: createWlift({ mode }).main for the page thread, worker for off-main; canvas + DOM bridges round-trip transparently.
✓Hatchfile-aware.runProject(wlift, src, hatchfileText) walks deps, fetches each .hatch bundle, and installs them before the user source runs.
✓Tier-up wasm JIT. Hot functions are emitted as fresh wasm modules at runtime (via wasm-encoder) and instantiated alongside the runtime — no extra dependency on the host browser's JIT.
// greet.wren — loaded by your hostclassGreeter {
statichello(name) {
System.print("hello, %(name) ✦")
}
}
# link against wrenlift$ cc host.c -o host \
-Iexamples \
target/release/libwren_lift.a$ ./host
hello, world ✦
// high-level: hatch-aware runtimeuse wren_lift::hatch_runner::HatchRunner;
fnmain() -> anyhow::Result<()> {
let mut runner = HatchRunner::new();
// install hatch packages
runner.install("@hatch:math")?;
runner.install("@hatch:fp")?;
// now run application codelet src = r#"
import "@hatch:math" for Vec3
System.print(Vec3.new(1, 2, 3))
"#;
runner.run_source("main", src)?;
Ok(())
}
// low-level: raw c api bindingsuse wren_lift::capi::*;
use std::ffi::CString;
fnmain() {
unsafe {
let mut cfg: WrenConfiguration = std::mem::zeroed();
wrenInitConfiguration(&mut cfg);
let vm = wrenNewVM(&mut cfg);
let module = CString::new("main").unwrap();
let src = CString::new(
"System.print(\"hello from rust\")"
).unwrap();
wrenInterpret(vm, module.as_ptr(), src.as_ptr());
wrenFreeVM(vm);
}
}
# Cargo.toml[dependencies]wren_lift = { git = "https://github.com/wrenlift/WrenLift.git", branch = "main" }
anyhow = "1"# build & run$ cargo run
Compiling wren_lift v0.1.16 Compiling host v0.1.0 Finished dev [unoptimized] in 4.1s Running `target/debug/host` hello, world ✦
<!-- Bare embed: one wlift.run() call. No package resolution. --><script type="module">import { createWlift } from"https://wrenlift.com/dist/wlift.js";
// `mode: "main"` runs on the page thread; "worker" off-mains.const wlift = awaitcreateWlift({ mode: "main" });
const { output } = await wlift.run(`
class Greeter {
static hello(name) { System.print("hello, %(name) ✦") }
}
Greeter.hello("browser")
`);
document.getElementById("out").textContent = output;
</script><pre id="out"></pre>
<!-- Hatch resolution: walk a hatchfile, fetch each @hatch:* package, install before the user source runs. Same flow the playground uses. --><script type="module">import { createWlift, runProject } from"https://wrenlift.com/dist/wlift.js";
const wlift = awaitcreateWlift({ mode: "main" });
const hatchfile = awaitfetch("./hatchfile").then(r => r.text());
const source = `
import "@hatch:assert" for Expect
import "@hatch:json" for JSON
var data = JSON.parse("{\"name\": \"wlift\", \"version\": 1}")
Expect.that(data["name"]).toBe("wlift")
Expect.that(data["version"]).toBe(1)
System.print("parsed %(data["name"]) v%(data["version"]) ✦")
`;
const { output } = awaitrunProject(wlift, source, hatchfile);
document.getElementById("out").textContent = output;
</script><pre id="out"></pre>
# hatchfile — declares the deps `runProject` will install.
name = "my-app"
version = "0.1.0"
entry = "main"[dependencies]"@hatch:assert" = "0.2.0""@hatch:json" = "0.1.0"
editor setup
Wire up your editor.
One-click install from the VS Code marketplace. Diagnostics, hover, goto-definition (cross-package), member completion after ., and an inline ▶ Run codelens — all driven by wlift-lsp, the language server bundled with every release.
✓Diagnostics + hover. Inline errors, type-aware hover cards on identifiers and calls.
✓Goto-definition. Local, workspace, and @hatch: dep sources — bundled .hatch sources extracted on demand.
✓Member completion after .. Receiver-aware suggestions.
✓Inline ▶ Run codelens. On main.wren and *.spec.wren; routes through hatch run when a hatchfile is in scope.
✓Auto-installs wlift-lsp. If the binary's missing, the extension offers a one-click install.sh and auto-restarts when the binary lands.
Or any LSP-speaking editor.
The same wlift-lsp binary the VS Code extension drives ships in every install tarball — drop it on PATH, point your editor at it, get the same diagnostics, hover, completion, and goto-definition surface.
✓Bundled. One install.sh drops wlift, hatch, and wlift-lsp on ~/.local/bin.
✓stdio transport. No socket setup, no daemon — your editor spawns the binary per workspace.
✓Editors. Helix, Neovim (vim.lsp 0.10+), and Zed configs in the panel; Sublime, Emacs, and Kakoune work from the same shape.
# install from a terminal$ code --install-extension Wrenlift.vscode-wrenlift installedwrenlift v0.1.2# open a .wren file — the LSP starts on first open.# missing the wlift-lsp binary? the extension offers# a one-click install and auto-restarts.$ code hello.wren
# or install from the Extensions panel search Wrenlift · click Install# direct marketplace URLmarketplace.visualstudio.com/items?itemName=Wrenlift.vscode-wrenlift# settings to override binary paths (optional):wrenlift.serverPath// wlift-lspwrenlift.wliftPath// wliftwrenlift.hatchPath// hatch