Docs · Toolchain
The `vyi` CLI
The toolchain is one binary, vyi, with five subcommands. This page is the
precise reference for each command, its flags, and its exit codes. To get
vyi on your machine, see Installing Vyi; for a narrative
walkthrough see the Tooling guide.
vyi build [bin] [flags] # compile to an artifact
vyi run [bin] [flags] [-- args…] # compile and execute
vyi check [bin] [flags] # typecheck only; report diagnostics
vyi test [dir] [flags] # run every *.tests.vyi under dir
vyi ast [bin] [--spans] [--pre-elaborate] # print AST as JSON
vyi help # print usageArgument ordering: flags come between the subcommand and the entry path.
Flag parsing stops at the first positional argument, so anything after the
entry is never treated as a vyi flag — for run it is forwarded to your
program as its arguments; for the other commands it is ignored.
vyi run --target x86-64-linux greeter # --target is vyi's flag
vyi run greeter --target x86-64-linux # --target is YOUR PROGRAM's argvRunning vyi with no arguments, or with an unknown command or flag, prints
usage and exits 2.
Commands
vyi build
Compiles the entry to an on-disk artifact and prints wrote <path> to stderr.
vyi build main.vyi # writes out/wasm/main
vyi build greeter # bin from package.vyipac entries
vyi build # every entry in the package
vyi build -o app.wasm greeter # choose the output path (single bin)
vyi build --target x86-64-linux greeter # out/x86-64-linux/greeter
vyi build --emit=wat main.vyi # WebAssembly text to stdout--emit selects the output kind:
--emit | Output | Default destination |
|---|---|---|
wasm (default) | a binary artifact: a .wasm module, or with --target x86-64-linux / arm64-linux a linked native executable | out/<target>/<bin> |
wat | WebAssembly text | stdout for a single bin (-o <path> writes a file; -o - is stdout); multi-bin needs -o <dir> |
asm | textual assembly for the active target — WebAssembly text on wasm, annotated x86-64 assembly on x86-64-linux | stdout, same -o rules as wat |
Any other --emit value is a usage error (exit 2).
Native builds (--target x86-64-linux / arm64-linux) link with your system C
compiler — the first of cc, gcc, or clang found on PATH; the build
fails with exit 1 if none is installed. See Targets.
The optional positional argument is either a .vyi file path or a bin name.
With a nearby package.vyipac (walked up from the current
directory), bin names come from its entries block — vyi build demo builds
the entry mapped to demo. Omitting the argument builds every declared
entry. A name that matches no entry, or a .vyi path that is not a declared
entry when the package lists entries, exits 2. The same rules apply to
run, check, and ast. Default artifacts land under out/<target>/<bin>
(for example out/wasm/demo). With -o and a single bin the flag is the full
output path; with multiple bins -o is an output root (-o dist →
dist/<target>/<bin>).
vyi run
Compiles in memory and executes immediately — no artifact is written.
vyi run main.vyi
vyi run main.vyi -- --verbose input.txt # forwarded to the program
vyi run --target x86-64-linux main.vyi # compile, link, exec nativelyEverything after the entry path becomes the program's arguments (readable via
@io's args()); a leading -- separator is conventional and stripped. On
the wasm target the program's own name (args().at(0)) is the entry file's
base name.
On the default wasm target the module runs under an embedded runtime with
WASI: the host's stdin/stdout/stderr pipe straight through, and the current
directory is mounted so the program can open files by paths relative to it.
With --target x86-64-linux or arm64-linux the compiler links a temporary
native executable and runs it directly.
The vyi run process exit code is your program's:
fn main() !u8returningOk(n)exits withn.- An error returned from
mainprintserror: <message>(the error's
@errmsg, plus its @errstack trace when it carries one) to stderr and
exits 1.
- On the
wasmtarget, a panic that unwinds out ofmainprints
panic: <message> and a stack trace to stderr and exits 101.
--timings appends one machine-readable line to stderr after the run:
VYI_TIMINGS compile_ns=<n> run_ns=<n>, prefixed with an ASCII record
separator (0x1E) so a wrapping tool can find and strip it. The playground's
server uses this; it is handy for benchmarking your own build loop too.
vyi check
Runs the compiler without writing an artifact and reports diagnostics — the
fast loop for editors and CI. It compiles all the way through code
generation, so anything build would reject, check reports.
$ vyi check main.vyi
ok: main.vyi
$ echo $?
0With problems, each diagnostic prints to stderr with a source excerpt and
caret, followed by a count line (2 errors), and the exit code is 1.
--json emits the same diagnostics as a JSON array on stdout instead — see
Diagnostics output.
vyi test
Discovers every *.tests.vyi file under the directory (recursively; default
.), compiles and runs each as a standalone program, and renders a pass/fail
tree with a summary. Writing test files is covered in the
Testing guide; a test file is ordinary Vyi:
import { it, expect } from "@testing"
// No `fn main` — `vyi test` synthesizes one that runs these
// top-level calls and exits with the failed-assertion count.
it("adds", () => {
expect(1 + 2).toBe(3)
})$ vyi test proj
✓ proj/math/add.tests.vyi (1 test)
✓ adds
❯ proj/math/sub.tests.vyi (3 tests | 2 failed)
✓ subtracts
× fails once
× fails twice
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
FAIL proj/math/sub.tests.vyi > fails once
❯ proj/math/sub.tests.vyi:8:5
FAIL proj/math/sub.tests.vyi > fails twice
❯ proj/math/sub.tests.vyi:12:5
Test Files 1 failed | 1 passed (2)
Tests 2 failed | 2 passed (4)The exit code is the number of test files that failed — 0 when
everything passes (or no test files exist), so it slots directly into CI. A
file counts as failed when any of its assertions fail, when it fails to
compile (shown as × <file> (unable to compile) after its diagnostics), or
when the program dies mid-run.
Each test file also works as a plain program: vyi run file.tests.vyi runs
it, and its own exit code is its failed-assertion count (clamped to 255).
vyi test supports --target wasm (default), --target x86-64-linux, and
--target arm64-linux. Native tests link with the system C compiler the same
way vyi run --target x86-64-linux / arm64-linux does.
vyi ast
Prints an abstract syntax tree as JSON on stdout. By default that is the
post-transform tree the compiler checked (after imports, fn desugar, and
elaborate rewrites such as ?T → Optional(T)). Pass --pre-elaborate for
the parse tree instead — source-faithful nodes like FnDecl,
OptionalType, and ErrorUnionType. The playground AST tab uses
--pre-elaborate.
vyi ast main # post-transform tree for the package bin
vyi ast --spans main # …with source spans (byte offsets) on each node
vyi ast --pre-elaborate --spans --file main.vyi # parse tree of one fileEach node carries a $kind discriminator ("File", "FnDecl",
"OptionalType", …) plus its fields. A file with parse errors still yields a
best-effort tree, and vyi ast exits 0 either way — it reports the tree, not
the diagnostics; use vyi check for those.
vyi help
vyi help (also -h / --help) prints the usage summary and exits 0.
Flags
| Flag | Commands | Meaning |
|---|---|---|
--target <name> | build, run, check, test, ast | Compilation target: wasm (default), x86-64-linux, or arm64-linux. Any other value exits 2. See Targets. |
--os <name> | build, run, check, test, ast | Override the recorded target OS. The target implies it (wasm → wasm, x86-64-linux / arm64-linux → linux). |
--packages <dir> | build, run, check, test, ast | Override the package search root (below). |
-o <path> | build | Output path for a single bin, or output root when building many bins. Default: out/<target>/<bin>; for --emit=wat/asm with one bin, stdout. |
--emit <kind> | build | wasm (default), wat, or asm. |
--timings | run | Print the VYI_TIMINGS line to stderr after the run. |
--json | check | Diagnostics as a JSON array on stdout instead of text on stderr. |
--spans | ast | Include source spans in the JSON. |
--pre-elaborate | ast | Dump the parse tree (before fn-desugar and elaborate), not the post-transform AST. |
--file <path> | ast | Package-relative .vyi to dump (library analysis; no bin required). |
Both --flag value and --flag=value forms work, as does a single dash
(-target wasm).
The package search root
@-packages (@io, @mem, @testing, …) are resolved from the first
directory found to contain the standard library, searched in order:
- the
--packages <dir>flag $VYI_STDLIB_PATH($VYI_PACKAGESis also accepted)$XDG_DATA_HOME/vyi(default~/.local/share/vyi), then each
$XDG_DATA_DIRS entry's vyi/, then /usr/share/vyi
- development fallbacks:
../packagesnext to thevyibinary, and
./packages and ../packages relative to the current
directory — so running from a source checkout just works.
When no candidate holds the standard library, every command fails with
no @core package found; set VYI_STDLIB_PATH (exit 1).
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success: clean check, artifact written, program exited 0, all tests passed. |
| 1 | The compile reported diagnostics; the entry file is missing or unreadable; a link/write step failed; or (run) the program returned an error from main. |
| 2 | Usage error: no/unknown command, missing entry argument, unknown flag, unsupported --target, unknown --emit, unknown bin name, missing test directory. |
n (run) | The program's own exit code — main's Ok payload, or 101 for an unhandled panic on the wasm target. |
n (test) | The number of test files that failed. |
Diagnostics output
The compiler reports everything it can find in one invocation — it does not
stop at the first error, and it keeps analyzing past parse errors. All
commands render diagnostics the same way: one stream, sorted by file position
and deduplicated, written to stderr in file:line:col form with a source
excerpt and caret:
bad.vyi:2:18: error: cannot assign ArrayPointer(u8) to i32
con a: i32 = "hi"
^^^^
bad.vyi:3:19: error: cannot assign i32 to bool
con b: bool = 3
^
2 errors(The trailing count line is check-only; build and run print just the
diagnostics.) Paths are relative to the compilation root — the nearest
ancestor directory holding a package.vyipac, or the entry file's own
directory without one — so diagnostics in imported files point at the right
file.
vyi check --json emits the identical, identically-ordered set as a JSON
array on stdout (the exit code is still 1 when it is non-empty, and it is
[] with exit 0 when clean):
[
{
"path": "bad.vyi",
"op": "notAssignable",
"severity": "error",
"message": "cannot assign ArrayPointer(u8) to i32",
"range": {
"start": { "line": 2, "col": 18 },
"end": { "line": 2, "col": 22 }
}
}
]| Field | Meaning |
|---|---|
path | File path, relative to the compilation root. |
op | Stable machine-readable code for the diagnostic kind. |
severity | error, warning, info, or hint. |
message | The human-readable text. |
range | 1-based line/column span; end points one past the last character. |
This is the format the playground and other tools consume; editors get the same diagnostics through the language server, which shares the compiler's front end (see the Tooling guide).