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] # print the final 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 linux-x64 greeter # --target is vyi's flag
vyi run greeter --target linux-x64 # --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 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 linux-x64 greeter # out/linux-x64/greeter
vyi build --emit=wat greeter # WebAssembly text to stdout--emit selects the output kind:
--emit | Output | Default destination |
|---|---|---|
wasm (default) | a binary artifact: a .wasm module, or with --target linux-x64 / linux-arm64 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 linux-x64 | stdout, same -o rules as wat |
Any other --emit value is a usage error (exit 2).
Native builds (--target linux-x64 / linux-arm64) 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 a bin name from the nearby
package.vyipac entries block (walked up from the current
directory) — vyi build demo builds the entry mapped to demo. Omitting the
argument builds every declared entry. Unknown bin names exit 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 greeter
vyi run greeter -- --verbose input.txt # forwarded to the program
vyi run --target linux-x64 greeter # 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 linux-x64 or linux-arm64 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@errstacktrace when it carries one) to stderr and exits 1. - On the
wasmtarget, a panic that unwinds out ofmainprintspanic: <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 linux-x64, and
--target linux-arm64. Native tests link with the system C compiler the same
way vyi run --target linux-x64 / linux-arm64 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), linux-x64, or linux-arm64. 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, linux-x64 / linux-arm64 → linux). |
--packages <dir> | build, run, check, test, ast | Override the package search root (below). |
-o <path> | build | Output path. Default: entry stem + .wasm (native: no extension); for --emit=wat/asm, 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_DIRSentry'svyi/, then/usr/share/vyi- development fallbacks:
../packagesnext to thevyibinary, and./packagesand../packagesrelative 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 as a headline, an arrow line locating it, and the source excerpt with a caret:
error: cannot assign [*con]u8 to i32
--> bad.vyi:2:18
|
2 | con a: i32 = "hi"
| ^^^^
note: expected type declared here
error: cannot assign i32 to bool
--> bad.vyi:3:19
|
3 | con b: bool = 3
| ^
note: expected type declared herePaths 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 [*con]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).