CLI framework · Zig 0.16+

Your folder structure is your CLI.

zcli is a batteries-included framework for command-line tools in Zig. Add a .zig file, get a command. Help text, completions, "did you mean?", prompts, progress bars, and docs — wired up at compile time for zero-cost dispatch.

$ curl -fsSL https://zcli.sh/install.sh | sh
0 runtime reflection 7 built-in plugins 8 prompt types MIT licensed
myapp — zsh
$ myapp deploy api --env staging
Deploying api to staging

$ myapp dploy api
Error: unknown command 'dploy'
  Did you mean deploy? (run with --help)

$ myapp users create --help
USAGE  myapp users create <name> [options]
  -a, --admin     Grant admin role
      --output   json · table · plain
 generated from your command metadata
What you get

Everything a polished CLI needs, none of the wiring.

The boring-but-essential parts of a command-line tool, generated for you and type-checked at compile time.

File-based commands

Create files in commands/ to add commands; nest folders for subcommands. The path becomes the command.

Type-safe parsing

Define args & options as Zig structs. Parsing is checked via comptime introspection — wrong types never compile.

Did-you-mean

Auto-generated --help, --version, shell completions, and typo suggestions out of the box.

Interactive prompts

Text, confirm, select, multi-select, password, search, number, and editor — all falling back gracefully without a TTY.

Progress & spinners

Nine spinner styles plus progress bars with ETA. Semantic colors adapt to your terminal's capabilities.

Docs generation

Emit markdown, man pages, and HTML straight from command metadata. Config loads transparently from JSON, TOML, or YAML.

How it works

Commands are discovered, not registered.

No registries to maintain, no macros to memorize. Commands are discovered and routing is generated at build time — there's no runtime filesystem scanning or reflection.

1 · Lay out your commands

Each .zig file maps to a command path. Add an index.zig to describe a group.

  • Folders become subcommand namespaces
  • Variadic args, flags, typed numbers — all from struct fields
  • Aliases & examples live in command metadata
src/commands/tree
src/commands/
├── init.zig               myapp init
├── deploy.zig             myapp deploy
└── users/
    ├── index.zig          myapp users
    ├── create.zig         myapp users create
    └── list.zig           myapp users list
src/commands/deploy.zigzig
const Context = @import("command_registry").Context;

pub const meta = .{
    .description = "Deploy your application",
    .options = .{
        .env = .{
            .short = 'e',
            .description = "Target environment",
        },
    },
};

pub const Args = struct {
    service: []const u8,
};

pub const Options = struct {
    env: []const u8 = "production",
};

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

2 · Write the command

Up to four exports: meta, Args, Options, and execute. The context hands you stdout/stderr, an allocator, the active theme, and plugin data.

  • Defaults on struct fields become option defaults
  • ?bool for optional flags, []const []const u8 for variadic
  • Dispatch is generated — zero-cost at runtime
Plugins

Seven plugins ship in the box.

Add them in build.zig. Plugins contribute global options, lifecycle hooks, and build-time config — store state in context.plugins.{id}.

PluginProvidesDefault
zcli_help--help / -h, auto-generated help textdefault
zcli_version--version / -Vdefault
zcli_not_found"Did you mean?" suggestions for typosdefault
zcli_completionsGenerate & install completions for bash, zsh, fishoptional
zcli_configTransparent config loading — JSON, TOML, YAML, per-command scopingoptional
zcli_output--output flag — json, table, plainoptional
zcli_github_upgradeupgrade command via GitHub releasesoptional
Plugins guide — defaults, auto-discovery & writing your own →
Standalone packages

Prompts and progress you can use anywhere.

zinput and zprogress work with or without the rest of zcli — no framework dependency required.

zinput — interactive prompts

Eight prompt types. Configurable prefix; line-based fallback when stdin isn't a TTY.

select + confirmzig
const idx = try zinput.select(writer, reader, .{
    .message = "Framework:",
    .choices = &.{ "express", "fastify", "koa" },
});
const ok = try zinput.confirm(writer, reader, .{
    .message = "Continue?", .default = true,
});

zprogress — spinners & bars

Nine spinner styles, progress bars with ETA, and semantic status states.

spinnerzig
var spinner = zprogress.spinner(.{ .style = .dots });
spinner.start("Loading...");
// ... do work, spinner.tick() in a loop ...
spinner.succeed("Done!");   // ✔ Done!
spinner.fail("Failed");    // ✖ Failed
spinner.warn("Warning");   // ⚠ Warning
Ready in two minutes

Stop wiring up arg parsers.

Add zcli to build.zig.zon, drop a file in commands/, and run zig build. That's the whole onboarding.