Key features:
- Typed flags with default values and help output - Rich formatting, and layout support - Command trees with isolated execution logic - It’s designed to feel good to use, not just to work. - Built for real-world CLI apps, not toy examples.
Would love feedback, feature ideas, or thoughts from other Zig devs.
repo here: https://github.com/xcaeser/zli
It's also important not to emit escape codes at all when TERM=dumb. (You'll get this behavior automatically if you implement color support by asking terminfo to the escape codes.)
const c = @cImport({
@cInclude("ncurses.h");
@cInclude("term.h");
});
pub fn main() !void {
// Initialize terminfo
_ = c.setupterm(null, 1, null);
// Get capability strings
const setaf = c.tigetstr("setaf"); // set foreground
const setab = c.tigetstr("setab"); // set background
const sgr0 = c.tigetstr("sgr0"); // reset
// Parameterize for red foreground
const red = c.tparm(setaf, c.COLOR_RED, 0, 0, 0, 0, 0, 0, 0, 0);
// Use it
_ = c.printf("%sThis is red%s\n", red, sgr0);
}A bunch of commonly used escape codes are pretty much universally identical. I wrote about this before: https://www.arp242.net/safeterm.html
By and large I think terminfo/$TERM is outdated, or at least partly. It's a 1970s/80s thing when terminals all did radically different things. But that hasn't been the case for decades now. You still need it for some things (kind of), but basic support like colours? Not so much.
> By and large I think terminfo/$TERM is outdated, or at least partly.
It's not outdated. It's a Chesterton's fence. Disregard for interoperability and feature discovery is why the terminal ecosystem has such immense difficulty getting traction on advanced features.
Please, no. There is no reason for this to be querying a hack from 1978 in 2025 when there are effectively two output terminal protocols--Windows and ANSI. And there is only a single terminal input protocol (kitty) that isn't brain damaged and allows you to do something super complex like "detect when Shift Key is pressed and released".
Ghostty actually does something like you ask, and I really wish it wouldn't. It's a pain in the ass with virtualization. I have Ghostty on my host, but I have to install Ghostty in all my guest instances to pick up some stupid termcap/terminfo entry, or I get garbage all over my terminal.
I bet it has a setting to set TERM. I mean, TERM is hardly a panacea, but it's better than blindly spewing terminal control control codes without configuration or control or feature discovery.
> effectively two output terminal protocols--Windows and ANSI. And there is only a single terminal input protocol (kitty) that isn't brain damaged and allows you to do something super complex like "detect when Shift Key is pressed and released".
And what, every terminal application should have its own special knob for you to tell it what kind of terminal you have?
This whole discussion is a great example of why Chesterton's fence is a thing. The terminal world is already pretty fragmented. Suggestions to ignore the only even halfway decent widely-supported app-agnostic config knob without replacing it with something better are short-sighted. In any multi-party ecosystem, you need some way for different programs, written by different people at different times, to discover each other's capabilities.
That's a bummer to hear, I've been wanting to use Ghostty for a while but that'd be a showstopper for me. Is there a GitHub issue tracking this?
Why not? The ad-hoc standard set of terminal emulator escape codes is much more widely supported than whatever is in terminfo.
You're not getting any compatibility bonuses via terminfo, only extra pain.
im always open to improvement, but i wanna keep it 100% zig.
If I were trying a program and saw that it disrespected me by ignoring a clear preference in my environment not to use colors, I wouldn't use that program again.
How do you like Zig compared to TypeScript? What would you like to see improved?
as Andrew (zig creator) said, zig makes you understand how computers work.
I recently wrote a CLI manually, thinking, CLIs are so simple why would you need a framework? At the end of writing it, I had just ended up writing my own CLI framework anyway. It gets tedious without one, even though none of the things the framework does are particularly complicated.
const now = ctx.flag("now", bool); // type-safe flag access
This is type-safe, but only at run time. Since your flags are (or could be) known at compile time, it would be nice to have this throw a compile error if the type is invalid.Or even better - fully lean into comptime and generate a struct so you can use field access without specifying a type:
const now = ctx.flag.now;