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.

sh
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 usage

Argument 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.

sh
vyi run --target x86-64-linux greeter     # --target is vyi's flag
vyi run greeter --target x86-64-linux     # --target is YOUR PROGRAM's argv

Running 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.

sh
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:

--emitOutputDefault destination
wasm (default)a binary artifact: a .wasm module, or with --target x86-64-linux / arm64-linux a linked native executableout/<target>/<bin>
watWebAssembly textstdout for a single bin (-o <path> writes a file; -o - is stdout); multi-bin needs -o <dir>
asmtextual assembly for the active target — WebAssembly text on wasm, annotated x86-64 assembly on x86-64-linuxstdout, 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 distdist/<target>/<bin>).

vyi run

Compiles in memory and executes immediately — no artifact is written.

sh
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 natively

Everything 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() !u8 returning Ok(n) exits with n.
  • An error returned from main prints error: <message> (the error's

@errmsg, plus its @errstack trace when it carries one) to stderr and exits 1.

  • On the wasm target, a panic that unwinds out of main prints

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.

sh
$ vyi check main.vyi
ok: main.vyi
$ echo $?
0

With 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:

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)
})
sh
$ 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 ?TOptional(T)). Pass --pre-elaborate for the parse tree instead — source-faithful nodes like FnDecl, OptionalType, and ErrorUnionType. The playground AST tab uses --pre-elaborate.

sh
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 file

Each 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

FlagCommandsMeaning
--target <name>build, run, check, test, astCompilation target: wasm (default), x86-64-linux, or arm64-linux. Any other value exits 2. See Targets.
--os <name>build, run, check, test, astOverride the recorded target OS. The target implies it (wasmwasm, x86-64-linux / arm64-linuxlinux).
--packages <dir>build, run, check, test, astOverride the package search root (below).
-o <path>buildOutput 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>buildwasm (default), wat, or asm.
--timingsrunPrint the VYI_TIMINGS line to stderr after the run.
--jsoncheckDiagnostics as a JSON array on stdout instead of text on stderr.
--spansastInclude source spans in the JSON.
--pre-elaborateastDump the parse tree (before fn-desugar and elaborate), not the post-transform AST.
--file <path>astPackage-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:

  1. the --packages <dir> flag
  2. $VYI_STDLIB_PATH ($VYI_PACKAGES is also accepted)
  3. $XDG_DATA_HOME/vyi (default ~/.local/share/vyi), then each

$XDG_DATA_DIRS entry's vyi/, then /usr/share/vyi

  1. development fallbacks: ../packages next to the vyi binary, 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

CodeMeaning
0Success: clean check, artifact written, program exited 0, all tests passed.
1The compile reported diagnostics; the entry file is missing or unreadable; a link/write step failed; or (run) the program returned an error from main.
2Usage 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:

text
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):

json
[
  {
    "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 }
    }
  }
]
FieldMeaning
pathFile path, relative to the compilation root.
opStable machine-readable code for the diagnostic kind.
severityerror, warning, info, or hint.
messageThe human-readable text.
range1-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).