Scaffold a production-ready Zig CLI application with zero external dependencies. Produces
a single static binary (~1MB) that cross-compiles to macOS, Linux, and Windows from any platform.
Designed for distribution to AI coding agents via curl | sh (or install.ps1 on Windows).
Reference Implementation
The Henry CLI at /Users/tbrewer/projects/goodway/henry/cli-zig/ is the reference
implementation this skill is based on. When in doubt about patterns, read those files.
Prerequisites
Verify before starting:
zig versionreturns 0.15.x or later- User has a directory for the project
Gather Input
Ask the user for:
- App name — binary name (lowercase, hyphen-separated, e.g.
my-tool) - Description — one-line description for root help
- Subcommands — initial commands to scaffold (e.g.
sync,deploy,status) - Config needed? — whether the app needs per-environment config (
~/.{{APP_NAME}}.json) - API client? — whether to include an HTTP client for a REST API (if yes, ask for base URL)
Generate Project
Read references/templates.md for all file templates.
Create files in this order:
1. Initialize project
mkdir -p <app-name>/src/commands
cd <app-name>
zig init
rm src/root.zig # No library consumers
2. Create files from templates
Apply placeholder substitution ({{APP_NAME}}, {{DESCRIPTION}}) to each template:
| File | Template | Notes |
|---|---|---|
build.zig |
build.zig template | Binary named {{APP_NAME}}, no deps |
build.zig.zon |
build.zig.zon template | Zero dependencies |
src/main.zig |
main.zig template | Command routing + arg helpers |
src/help.zig |
help.zig template | All help text, agent-readable |
src/commands/<subcmd>.zig |
subcommand template (one per command) | |
justfile |
justfile template | build/test/dist/release recipes |
install.sh |
install.sh template | macOS/Linux installer |
install.ps1 |
install.ps1 template | Windows PowerShell installer |
.gitignore |
.gitignore template | |
CLAUDE.md |
CLAUDE.md template |
If config is needed, also create:
| File | Template |
|---|---|
src/config.zig |
config.zig template |
src/commands/configure.zig |
configure.zig template |
If API client is needed, also create:
| File | Template |
|---|---|
src/generated.zig |
generated.zig template |
src/table.zig |
table.zig template |
2b. OpenAPI client generation (if user has an OpenAPI spec)
If the user has an OpenAPI JSON spec, use openapi2zig to seed the types and client:
# Install openapi2zig if not present
which openapi2zig || curl -fsSL https://christianhelle.com/openapi2zig/install | INSTALL_DIR="$HOME/.local/bin" bash
# Generate from spec
~/.local/bin/openapi2zig generate -i <spec-path> -o src/generated.zig
CRITICAL: The generated output will NOT compile on Zig 0.15.2. You MUST manually fix it:
- Function names —
HenryWeb.Api.V1.Controller.indexis invalid Zig. Replace with valid identifiers likelistItems,getItem,createItem. - Nested types flattened — Fields like
hero: ?[]const u8should behero: ?Herowith proper struct types. Fix all$reffields. - Old API calls — Replace
std.http.Client.init(allocator)withstd.http.Client{ .allocator = allocator }. Replacestd.ArrayList(u8).init(allocator)withvar list: std.ArrayList(u8) = .{};. Replacestd.json.stringifywithstd.json.fmt. - No auth headers — Add Bearer token auth via
extra_headersinfetch(). - No response handling — Functions return
!void. Change to returnRawResponsewith status + body. - No base URL — Functions use relative paths. Add a
Clientstruct that holdsbase_urland prepends it.
See the generated.zig template in references/templates.md for the corrected pattern.
The reference implementation at /Users/tbrewer/projects/goodway/henry/cli-zig/src/generated.zig shows a complete working example.
Add a generate recipe to the justfile:
# Regenerate API client from OpenAPI spec (requires manual fixes after)
generate:
~/.local/bin/openapi2zig generate -i <spec-path> -o src/generated.zig
@echo "IMPORTANT: Generated code needs manual fixes for Zig 0.15.2 — see CLAUDE.md"
3. Fix fingerprint
The first zig build will fail with an invalid fingerprint error and suggest the correct value. Run:
zig build 2>&1
Then update build.zig.zon with the suggested fingerprint value and build again.
4. Verify
zig build
zig build test
zig build run -- --help
Zig 0.15.2+ Critical Patterns
These are non-obvious and will cause compilation failures if done wrong:
- stdout/stderr — Use
std.fs.File.stdout(), notstd.io.getStdOut()(removed in 0.15) - ArrayList — Unmanaged:
var list: std.ArrayList(u8) = .{};then pass allocator to each method:list.append(allocator, x),list.deinit(allocator) - Formatted output — Use
std.fmt.allocPrint(allocator, fmt, args)thenFile.stdout().writeAll(msg) - JSON serialization — Use
std.json.fmt(value, .{.whitespace = .indent_2})withstd.fmt.allocPrint("{f}", .{...}) - JSON parsing —
std.json.parseFromSlice(T, allocator, content, .{.ignore_unknown_fields = true, .allocate = .alloc_always}) - HTTP client — Use
std.http.Client.fetch()withstd.Io.Writer.Allocatingto capture response body. No.open()method. - Table rows in loops — Heap-allocate row slices:
const row = try allocator.alloc([]const u8, n);. Stack-allocated&.{...}gets overwritten each iteration. - main signature —
pub fn main() !void(no Init parameter in 0.15.2) - Allocator for CLI — Use
std.heap.page_allocatorfor short-lived CLI processes. GPA reports false leak warnings on exit.
Help System Design
CRITICAL: The help system is designed to be read by AI coding agents. Every command must have:
- Usage line with exact syntax
- All flags with types and descriptions
- All positional arguments with descriptions
- Behavior notes (what happens when, defaults, side effects)
- Exit codes
- Examples showing common use cases
When adding a new command, you MUST update THREE files:
src/main.zig— routing inmain()anddispatchHelp()src/help.zig— add help constant AND update parent command's help listingsrc/commands/<name>.zig— implementation
Output
After generation, print:
Created {{APP_NAME}}/ with:
build.zig — build config (zero dependencies)
build.zig.zon — package manifest
src/main.zig — CLI entry point + arg parsing
src/help.zig — comprehensive help text (agent-readable)
src/commands/*.zig — command implementations
justfile — build/test/dist/release recipes
install.sh — macOS/Linux installer (auto-detects OS/arch)
install.ps1 — Windows PowerShell installer
CLAUDE.md — AI agent instructions
Binary: zig-out/bin/{{APP_NAME}} (~1MB release)
Run it:
cd {{APP_NAME}} && zig build run -- --help
Cross-compile all platforms:
just dist
Next steps:
- Implement command logic in src/commands/*.zig
- Update help text in src/help.zig for each command
- Run `zig build test` to verify
- Run `just dist` to build release binaries