Blog

Introducing zcli: your folder structure is your CLI

I kept writing the same argument-parsing boilerplate for every command-line tool I built in Zig, then re-wiring a registry every time a command moved. zcli is the framework I wanted: drop a .zig file in commands/, and it's a command — discovered at build time, routed with zero runtime cost, and type-checked by the compiler instead of by hand.

The idea

A command is one file, up to four exports: meta for help text, Args and Options as plain structs, and an execute function. There's no builder chain to keep in sync, no registry to forget to update — the file is the command.

src/commands/deploy.zigzig
pub const meta = .{ .description = "Deploy your application" };
pub const Args = struct { service: []const u8 };
pub const Options = struct { env: []const u8 = "production" };

pub fn execute(args: Args, options: Options, context: anytype) !void {
    try context.stdout().print("Deploying {s} to {s}\n", .{ args.service, options.env });
}

What v0.19.0 adds

This release is hardening and tooling: zcli add/rm/mv restructure command files in place via an AST splice engine, zcli guide gives your editor — or your coding agent — a version-matched reference, and the zcli_secrets plugin stores credentials in the OS keychain instead of a plaintext config file. Windows joined the first CI tier, and two audit passes closed 50+ findings across the parse pipeline and the PTY test harness. Full detail in the changelog.

Try it

terminalsh
$ curl -fsSL https://raw.githubusercontent.com/ryanhair/zcli/main/install.sh | sh
$ zcli init myapp && cd myapp && zig build
$ ./zig-out/bin/myapp hello World

It's MIT licensed, targets stable Zig 0.16.0, and the comparison to zig-clap and zli is on the site if you're weighing options. Issues and PRs welcome on GitHub.