writing-zig-cli-tools
Skill2 starsChanged 8 months agoMITView on GitHub
Writes CLI tools in Zig using modern 0.15+ syntax and best practices. Use when creating command-line applications in Zig, parsing CLI arguments, setting up Zig project structure with build.zig, or working with Zig's I/O and error handling for CLI programs.
In this repository29 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- building-lit-componentsSkill
- building-static-site-rss-feedsSkill
- building-templ-componentsSkill
- checking-wsh-vrtSkill
- committing-codeSkill
- creating-agent-skillsSkill
- creating-test-casesSkill
- deploying-with-isucon-ansibleSkill
- designing-isucon-architectureSkill
- frontend-designSkill
- operating-isucon-servers-via-sshSkill
- planning-wsh-improvementsSkill
- porting-isucon-rust-to-goSkill
- releasing-zig-with-goreleaserSkill
- running-isucon-as-an-agentSkill
- sharing-sockets-with-so-reuseport-in-zigSkill
- tuning-isucon-linux-and-nginxSkill
- using-go-tool-directiveSkill
- using-grafana-mcpSkill
- using-unocss-with-templSkill
- winning-isuconSkill
- winning-web-speed-hackathonSkill
- writing-feature-specSkill
- writing-implementation-tasksSkill
- writing-isucon-arch-subagent-tasksSkill
- writing-project-constitutionSkill
- writing-technical-designSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 237
Always flush stdout/stderr: before program exit
- Very specific: line 241
Use
b.addLibrary(): instead of removed addStaticLibrary()
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Writing Zig CLI Tools
Build high-performance CLI tools in Zig using modern 0.15+ idioms. Covers project setup, argument parsing, I/O, error handling, and cross-compilation.
Use this skill when creating CLI applications in Zig, parsing command-line arguments, setting up build.zig projects, or writing robust terminal I/O code.
Supporting files: SYNTAX-GUIDE.md for 0.15+ syntax changes, BUILD-SYSTEM.md for build.zig patterns, ARGUMENT-PARSING.md for CLI argument handling.
Quick Start
Project Structure
my-cli/
├── build.zig
├── build.zig.zon
└── src/
└── main.zigMinimal build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "my-cli",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_cmd.step);
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}Minimal build.zig.zon
.{
.name = .{ "my-cli" },
.version = "0.1.0",
.fingerprint = .auto,
.minimum_zig_version = "0.15.0",
.paths = .{ "build.zig", "build.zig.zon", "src" },
.dependencies = .{},
}Minimal main.zig (0.15+ I/O)
const std = @import("std");
pub fn main() !void {
var stdout_buf: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buf);
const stdout = &stdout_writer.interface;
var stderr_buf: [4096]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&stderr_buf);
const stderr = &stderr_writer.interface;
// Parse arguments
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var args = try std.process.argsWithAllocator(allocator);
defer args.deinit();
_ = args.next(); // skip program name
const filename = args.next() orelse {
try stderr.print("Usage: my-cli <filename>\n", .{});
try stderr.flush();
std.process.exit(1);
};
try stdout.print("Processing: {s}\n", .{filename});
try stdout.flush();
}Critical: 0.15+ I/O Pattern
Zig 0.15+ uses buffered I/O by default. Always flush before exit:
var buf: [4096]u8 = undefined;
var writer = std.fs.File.stdout().writer(&buf);
const stdout = &writer.interface;
try stdout.print("output\n", .{});
try stdout.flush(); // REQUIRED - output won't appear without thisFor unbuffered I/O (debugging, progress output):
var writer = std.fs.File.stderr().writer(&.{});
const stderr = &writer.interface;
try stderr.print("debug: immediate output\n", .{});
// No flush needed with empty bufferArgument Parsing with zig-clap
For complex CLI tools, use zig-clap:
zig fetch --save git+https://github.com/Hejsil/zig-clapAdd to build.zig:
const clap = b.dependency("clap", .{});
exe.root_module.addImport("clap", clap.module("clap"));Usage:
const clap = @import("clap");
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const params = comptime clap.parseParamsComptime(
\\-h, --help Display help and exit.
\\-o, --output <str> Output file path.
\\-v, --verbose Enable verbose output.
\\<str>...
\\
);
var diag = clap.Diagnostic{};
var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{
.diagnostic = &diag,
.allocator = gpa.allocator(),
}) catch |err| {
diag.report(err);
return err;
};
defer res.deinit();
if (res.args.help != 0) {
return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{});
}
for (res.positionals) |pos| {
std.debug.print("positional: {s}\n", .{pos});
}
}Error Handling Patterns
pub fn main() !void {
run() catch |err| {
var stderr_buf: [4096]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&stderr_buf);
const stderr = &stderr_writer.interface;
try stderr.print("Error: {}\n", .{err});
try stderr.flush();
std.process.exit(1);
};
}
fn run() !void {
// Application logic here
// Errors propagate naturally with `try`
}File I/O
fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
return try file.readToEndAlloc(allocator, std.math.maxInt(usize));
}
fn writeFile(path: []const u8, data: []const u8) !void {
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(data);
}Cross-Compilation
Build for multiple targets:
zig build -Dtarget=x86_64-linux
zig build -Dtarget=aarch64-macos
zig build -Dtarget=x86_64-windowsKey 0.15+ Gotchas
- Always flush stdout/stderr before program exit
- ArrayList is unmanaged - pass allocator to each method:
try list.append(allocator, val) - Type tags are lowercase -
.int,.@"struct",.pointer - Use
b.createModule()in build.zig, notroot_source_filedirectly - Use
b.addLibrary()instead of removedaddStaticLibrary() - Calling convention is
.c(lowercase), not.C
See SYNTAX-GUIDE.md for complete 0.15+ migration details.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

