Command Line
One command, j2, runs, builds, formats, and tests J2
programs, and holds the keys to the capability sandbox.
Commands
| Command | Description |
|---|---|
j2 FILE.j2 [args...] | Run a program, interpreter first: instant start, with a silent native fallback for anything the interpreter does not support. |
j2 run FILE.j2 | Run through the interpreter explicitly. |
j2 build FILE.j2 -o OUT | Compile to a native binary with automatic parallelization. The right mode for compute-heavy programs you run more than once. |
j2 emit-native FILE.j2 | Print the lowered native source, for the curious and for debugging. |
j2 fmt [-w] FILE.j2 | Format a source file; -w rewrites it in place, otherwise the result prints to stdout. |
j2 repl | Interactive session. Bindings persist, expressions print, :quit exits. |
j2 test [DIR] | Run every J2 file under a directory and report pass and fail counts. |
j2 --version, j2 --help | Version and usage. |
Program arguments
Anything after the file name is handed to the program, available through
proc.argv(). Element 0 is the program itself, so real arguments start at
index 1:
# greet.j2
args = proc.argv()
if count(args) > 1 {
print(fmt("hello, {}", args[1]))
} else {
print("usage: greet NAME")
}
$ j2 run greet.j2 Ada
hello, Ada
Capabilities
Programs run inside a deny-by-default sandbox. The parts of the standard library that
reach outside the process are switched off until a flag grants them, so running an
unfamiliar script cannot quietly read files, spawn programs, or phone home. A denied
call raises RuntimeError naming the flag it needs.
| Flag | Grants |
|---|---|
--allow-fs | the fs module: reading and writing files |
--allow-proc | proc.run: spawning processes |
--allow-net | the http module: network requests |
--allow-unsafe | the rust { } escape hatch (see How J2 Runs) |
--allow-all | everything above |
$ j2 run report.j2 # no capabilities
$ j2 run --allow-fs report.j2 # may touch files
$ j2 run --allow-fs --allow-net sync.j2
Grant the narrowest set that lets the program do its job, and treat a script that
asks for --allow-all the way you would treat one asking for your
password.
Environment variables
| Variable | Effect |
|---|---|
J2_FORCE_NATIVE=1 | Skip the interpreter and run through the native engine, without producing a binary. |
J2_NO_NATIVE=1 | Stay on the dynamic path instead of native lowering. Mostly for testing and triage. |
J2_PARALLEL=0 | Build natively but with automatic parallelism off; single threaded. |
J2_NO_NESTED=1 | Disable only the nested-loop parallel patterns (matrix kernels), keeping the rest. |
J2_DEBUG=1 | On a native build failure, show the underlying compiler diagnostics instead of the one-line summary. |
J2_PATH | Colon-separated directories searched by import; see Imports. |
J2_TRUSTED=1 | Equivalent to --allow-unsafe. |
J2_ALLOW_LOCAL_NET=1 | Let http reach loopback and private addresses, which are otherwise refused even under --allow-net. |
When a native build fails
If a program cannot be compiled natively, j2 prints a single line saying
so rather than pages of internal diagnostics:
internal error: could not compile the program to native code
(re-run with J2_DEBUG=1 for details, or J2_NO_NATIVE=1 to use the interpreter)
The two suggestions are the two useful moves: J2_DEBUG=1 to see the real
error when reporting a bug, and J2_NO_NATIVE=1 or j2 run to
keep working through the interpreter meanwhile.
Exit status
A program that completes exits with status 0. An uncaught error prints its one-line report and exits nonzero, so J2 scripts compose with shell tooling and CI the way you would expect.