12 - Tooling
Good tooling is as important as good syntax — a clean language with a poor workflow is a
frustrating one to use day to day. Everything here is driven by the single verge binary.
The Verge CLI
These are the commands you’ll use most:
| Command | What it does |
|---|---|
verge run [path] | Compile and run a file or project |
verge build [path] | Compile to a binary and stop |
verge test [path] [filter] | Run tests, optionally filtered |
verge fmt [path] | Format source in place |
verge init [dir] | Scaffold a new project |
verge add <url>@<version> | Add a dependency to verge.toml |
verge fetch | Fetch all declared dependencies |
verge bind <lib|header.h> | Generate Verge bindings from a C header |
verge lsp | Start the language server |
verge mcp | Start the MCP server |
That’s the core loop: create, build, run, test, format, repeat — and generate C bindings when you need to interface with a native library.
Building and running
Reach for verge run during fast iteration and verge build when you just want the
artifact. Both accept a single file or, with no argument, the project in the current
directory (they read verge.toml and start from main.v):
verge run hello.v # single file
verge run # the project in this directory
verge build # compile the project, leave the binary
Formatting with verge fmt
verge fmt rewrites source into the canonical layout in place, so code style stays
consistent without any manual effort or team debate. It’s idempotent — running it twice in a
row produces no second round of changes:
verge fmt # format the whole project
verge fmt hello.v # or one file
The recommended habit is: write code, run verge fmt, then commit.
Testing
Tests live inline in .v files, right next to the code they judge. A file made up entirely
of test blocks is perfectly valid — no main required — and blocks can nest to group
related cases:
test "addition works" {
assert(1 + 1 == 2)
}
test "math" {
test "subtraction" {
assert(5 - 3 == 2)
}
}
Run them with verge test:
ok addition works
ok math/subtraction
2 passed, 0 failed
Filter to a group with verge test "math", or a single nested case with
verge test "math > subtraction". That’s enough to work a tight red-green-fix loop without
slicing files apart by hand.
Editor support with verge lsp
verge lsp starts the language server. Point your editor’s LSP client at it and you get the
usual quality-of-life features:
- diagnostics as you type
- hover with types and doc comments
- go-to-definition
- context-aware completions
- signature help
The editor handles the JSON-RPC plumbing; the language server handles the analysis, so you can focus on the code itself.
Automation with verge mcp
verge mcp starts an MCP server so tools and AI agents
can drive the toolchain in a structured way — compiling, running, type-checking, and querying
LSP features over a defined protocol — rather than parsing CLI output. If you’re building
agent workflows or custom editor automation around Verge, this is the interface to use.
Projects and dependencies
verge init myproject scaffolds a starter project with a verge.toml and a main.v.
Dependencies are git-based for now:
verge add github.com/user/[email protected] # record a dependency
verge fetch # download everything in verge.toml
It’s simple — refreshingly minimal or suspiciously early, depending on your mood.
C bindings with verge bind
Writing FFI glue by hand is tedious and error-prone, so verge bind reads a C library or
header and generates the Verge declarations for you. The generated output is a link block —
the same form you’d write by hand:
link "curl" extern from "curl/curl.h" {
curl_easy_init: f() => *u8
curl_easy_setopt: f(handle: *u8, option: i32, value: *u8) => i32
}
Treat the output as a starting point — review the types and add higher-level wrappers as
needed. (See Advanced Features for a runnable link example.)
Build targets
By default Verge builds a native binary for your machine. Additional targets are declared in
verge.toml under [[target]], each with a name and platform, so a project can describe
more than one build output. If you omit the section entirely, a single native target is
assumed.
A sensible daily routine
A normal Verge loop looks like this:
verge inita project if you need one- write code, with
verge lsprunning in your editor verge fmtverge testverge runorverge build- reach for
verge bindorverge mcponly when the problem calls for it
Compact and predictable — just enough tooling to let you focus on the code itself.