Getting started

From zero to a working CLI in three commands.

Install the zcli binary, scaffold a project with zcli init, then grow it one file at a time with zcli add command. Each step below shows exactly what runs and what lands on disk.

Requires Zig 0.16+ 4 plugins wired in on init MIT licensed
01

Install the zcli binary

One line downloads the latest release for your OS and drops the zcli scaffolding tool into ~/.local/bin. This is the meta-CLI you'll use to create projects and generate commands — it is not a dependency your app ships with.

Run

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

The script detects your platform, pulls the matching binary from the latest GitHub release, verifies the checksum when available, and marks it executable.

Prefer building from source?

Clone and build with Zig directly — artifacts land in zig-out/bin/.

$ git clone https://github.com/ryanhair/zcli.git
$ cd zcli && zig build

What happens

install — zsh
$ curl -fsSL …/install.sh | sh
==> Detecting platform… macos / aarch64
==> Fetching latest release… zcli v0.19.0
==> Downloading binary…
==> Verifying checksum…
 Installed zcli to ~/.local/bin/zcli

$ zcli --version
zcli 0.19.0

$ zcli --help
Build beautiful CLIs with zcli — scaffold
projects, add commands, and more.

COMMANDS
  init   Initialize a new zcli project
  add    Add commands and plugins
  tree   Visualize your command structure

If zcli isn't found afterward, make sure ~/.local/bin is on your PATH.

02

Initialize a new project

zcli init scaffolds a complete, buildable project: a build.zig.zon that pulls in zcli, a build.zig that wires up zcli.generate with the built-in plugins, an entrypoint, and one working example command. It fetches dependencies so zig build works immediately.

Run

$ zcli init myapp --description "My awesome CLI"

Pass . instead of a name to initialize in the current directory (it must be empty). --version sets the starting version; both options are optional.

What happens

myapp — zsh
$ zcli init myapp
Creating new zcli project: myapp
  Creating build.zig.zon…
  Creating build.zig…
  Creating src/main.zig…
  Creating example command (hello)…
  Fetching dependencies…

 Project 'myapp' created successfully!

Next steps:
  cd myapp
  zig build
  ./zig-out/bin/myapp hello World
  ./zig-out/bin/myapp --help

What you get

myapp/scaffold
myapp/
├── build.zig.zon     # project + zcli dependency
├── build.zig         # zcli.generate + builtin plugins
└── src/
    ├── main.zig      # generated entrypoint
    └── commands/
        └── hello.zig  myapp hello

build.zig already registers the help, version, not-found, and completions plugins — you get --help and "did you mean?" for free.

Build it & run the example

myapp — zsh
$ cd myapp && zig build
$ ./zig-out/bin/myapp hello World
Hello, World!
$ ./zig-out/bin/myapp hello Alice --loud
HELLO, Alice!

That output comes from the generated src/commands/hello.zig — a real command with a typed name argument and a --loud flag. Open it to see the shape every command follows.

03

Add a new command

zcli add command writes a ready-to-edit command file at the right path under src/commands/ — with meta, Args, Options, and an execute() stub already filled in. Nested paths like users/create create the folders for you. Run it from your project root (where build.zig lives).

Run

$ zcli add command deploy --description "Deploy your application"

Because the path becomes the command, zcli add command users/create produces myapp users create and creates the users/ namespace automatically.

What happens

myapp — zsh
$ zcli add command deploy \
    --description "Deploy your application"

 Created src/commands/deploy.zig

  Next steps
    1. Implement execute() in
       src/commands/deploy.zig
    2. zig build
    3. ./zig-out/bin/myapp deploy --help

Run it outside a project and it stops with Not in a zcli project directory — no stray files.

The file it generates

src/commands/deploy.zigzig
const std = @import("std");
const zcli = @import("zcli");
const Context = @import("command_registry").Context;

pub const meta = .{
    .description = "Deploy your application",
    .examples = &.{
        "deploy",
    },
};

pub const Args = struct {};
pub const Options = struct {};

pub fn execute(args: Args, options: Options, context: *Context) !void {
    _ = args;
    _ = options;
    const stdout = context.stdout();
    try stdout.print("TODO: Implement deploy\n", .{});
}

Fill in execute(), then zig build — routing regenerates and myapp deploy is live with auto-generated --help.

Add args & options to a command

Args and options are their own scaffolders — zcli add arg and zcli add option splice a typed field into an existing command's struct in place, so you don't hand-edit the boilerplate.

$ zcli add arg deploy service --type []const u8
$ zcli add option deploy env --type []const u8 --short e --nullable
+

See your whole CLI at a glance

zcli tree reads src/commands/ and prints the structure it discovered — no build required. Add --show-options to include each command's arguments and options.

Run

$ zcli tree

Discovery reuses the same scan the build runs, so the tree always reflects what's actually on disk — folders show as (group) namespaces.

What happens

myapp — zsh
$ zcli tree
├── deploy [Deploy your application]
└── hello [Say hello to someone]

$ zcli tree --show-options
├── deploy [Deploy your application]
└── hello [Say hello to someone]
    args:    <name:[]const u8>
    options: --loud
That's the whole loop

Install · init · add. Ship the rest.

You now have a buildable CLI and a one-liner to grow it. Next: wire up options, add plugins, and generate docs from your command metadata.