Documentation

A batteries-included framework for building command-line interfaces in Zig. Drop .zig files in a directory and get a fully-featured CLI — with command discovery and routing wired up at compile time for zero-cost dispatch.

Requires Zig 0.16.0 or newer. zcli is currently v0.25.0 and MIT licensed.

Quick start3 commands

Install the meta-CLI, scaffold a project, add a command. That's the whole loop.

1

Install the zcli CLI

See the install page for every option, including shell completions.

terminalsh
$ curl -fsSL https://zcli.sh/install.sh | sh
2

Scaffold a project

terminalsh
$ zcli init myapp --description "My awesome CLI"
Creating new zcli project: myapp
  Creating build.zig.zon...
  Creating build.zig...
  Creating src/main.zig...
  Creating example command (hello)...
  Creating README.md...
  Creating AGENTS.md...
  Initializing git repository...
  Fetching dependencies (this may take a moment)...
 Build verified

 Project 'myapp' created successfully!
3

Add a command & build

terminalsh
$ cd myapp
$ zcli add command deploy --description "Deploy your application"
 Created src/commands/deploy.zig

$ zig build
$ ./zig-out/bin/myapp --help
USAGE  myapp <command> [options]
  deploy   Deploy your application
  hello    Say hello

Add more commands the same way — start with Commands & structure for the file-path rules, or AI agents if a coding agent is driving.

Manual setup — wiring build.zig by hand — skip if zcli init worked
1

Add zcli to build.zig.zon

terminalsh
$ zig fetch --save https://github.com/ryanhair/zcli/archive/refs/tags/v0.25.0.tar.gz
2

Wire up build.zig

build.zigzig
const zcli = @import("zcli");

const zcli_dep = b.dependency("zcli", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("zcli", zcli_dep.module("zcli"));

const cmd_registry = try zcli.generate(b, exe, zcli_dep, .{
    .commands_dir = "src/commands",
    .plugins = &.{
        zcli.builtin(.help, .{}),
        zcli.builtin(.version, .{}),
        zcli.builtin(.not_found, .{}),
    },
    .app_name = "myapp",
    .app_description = "My awesome CLI",
});
exe.root_module.addImport("command_registry", cmd_registry);
3

Write your first command

src/commands/hello.zigzig
const Context = @import("command_registry").Context;

pub const meta = .{ .description = "Say hello" };
pub const Args = struct { name: []const u8 };
pub const Options = struct {};

pub fn execute(args: Args, _: Options, context: *Context) !void {
    try context.stdout().print("Hello, {s}!\n", .{args.name});
}

The full config — plugins_dir, shared modules, docs generation, per-command tests — is in the build integration guide.

Core

Defining a CLI: how files become commands, how types become parsers, and what a command has to work with.

Terminal UX

Talking to the user: prompts, progress, live layouts, full-screen TUIs — all themed from one declaration, all degrading cleanly when piped.

Services

The batteries a real CLI ends up needing — config, credentials, and the network — with safe behavior as the only behavior.

Extend & verify

Workflow & shipping