Odin for everything you used to reach for Python to do.
Scripts that start like bash and run like C. Batteries linked in. A UI toolkit you can test without a window.
Scripts · Packages · UI · Streams · Fuzzing · Setup
#!/usr/bin/env odin-run
package main
import "core:fmt"
import "jm:prelude"
import "jm:sh"
import "jm:path"
must :: prelude.must
main :: proc() {
context = prelude.init()
branch := must(sh.out("git rev-parse --abbrev-ref HEAD"))
for f in must(path.walk(".")) {
fmt.println(branch, f)
}
}
chmod +x and run it. The first run compiles; every run after starts the cached binary.
Write it like a script
The prelude puts an arena in the allocator, so a script never frees. Anything that can fail
goes through must, which dies with the call site and logs the death. The shell, HTTP, paths,
time and tar are one import each. When a script outgrows a script, it is already a native
program. Writing scripts →
Ship one file
SQLite, zstd, wasm3, libgit2 and PostgreSQL’s own parser are vendored and linked statically, so a program that uses them installs nothing. libcurl and libpq are the only system libraries. A shipped binary updates itself from a signed release, by patch where it can: 197 KB instead of 946 KB between two builds 15 commits apart. Packages →
A UI whose frame is data
jm:ui is immediate mode, and every frame is plain arrays: a scene, a draw list, a hit list.
A test clicks and types into it with no window. A change is checked as a text dump or a
headless PNG, and the window hot-reloads the app from a child process on every build. Screen
readers hear it through AccessKit on all three platforms. UI →
Material 3 Expressive, Fluent 2 and GitHub Primer, generated from each system's own tokens. Every screenshot here was rendered headlessly by jm itself.
Pipelines that never block
jm:stream builds typed operators into a DAG of bounded edges. A node runs only when an edge
changed and yields instead of waiting, so there is no thread per stage and no select. A stage
hop costs about 20 ns, and the same pipeline runs on one thread for a deterministic test or on
a pool in production. Streams →
Fuzzed, not hoped
Nine packages carry a property suite for jm:fuzz, with shrinking, a corpus and replayable
seeds, and just test replays every corpus. The suites have found real bugs on their first runs:
three crashes in the tar reader, a pointer check in the wasm host that wrapped, and invalid
UTF-8 that PostgreSQL’s parser passes straight through. Fuzzing →
Linux, macOS, Windows
just check type-checks every package for all three targets from one machine. CI builds and
tests every package on each of them. Building and testing →
Get started
git clone https://mordaunt.dev/code/jm && cd jm
./setup.sh # install what the recipes need, report what is missing
just odin-run-install # odin-run into ~/.local/bin, pointed at this checkout
just odin-run-example # run examples/hello.odin
Then open a kitchen to see the UI: just material-kitchen, just fluent-kitchen or
just primer-kitchen. Setup →
Learn more
| Guide | What it covers |
|---|---|
| Setup | Toolchains and system libraries per platform |
| Writing scripts | Conventions, logging, the odin-run cache |
| Packages | Every package and what it does |
| Building and testing | just recipes and CI |
| UI | The frame model, needs and commands, hot reload, the example apps |
| Streams | Dataflow pipelines and their measured costs |
| Fuzzing | Property suites, isolation, and what they found |
| SQLite · zstd · WebAssembly | The vendored C libraries and how they are built |
| PostgreSQL · Git | The SQL parser, the libpq client and libgit2 |
Licence
Copyright 2026 Jack Mordaunt. jm is licensed under the Apache License 2.0. The vendored libraries keep their own licences, recorded beside their source or in their guide above. The jm name and mark are not covered by the code licence; see branding.