Show HN: Zli – A Batteries-Included CLI Framework for Zig
64 caeser 32 5/25/2025, 4:52:14 PM github.com ↗
I built zli, a batteries-included CLI framework for Zig with a focus on DX and composability.
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
Or even better - fully lean into comptime and generate a struct so you can use field access without specifying a type:
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.
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.
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.)
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.
> It's a Chesterton's fence.
It's not. The world has changed. Everyone uses \x1b[1m to make text bold today but in the past there were a few dozen ways from different vendors of (hardware) terminals to make text bold. But this situation no longer exists. Like I already said: you still need it for some things – there's a reason I compiled a "safe terminal escape code" list. But for many things you don't.
I never claimed that. The sudo or ssh configuration is up to you. Or else there's |cat, which often works when NO_COLOR doesn't.
Either way, terminfo was never intended to allow users to disable colours. Certainly not via TERM=dumb – that was always a hack. If you want to disable colours then there are better ways.
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.
The project in question, however, does seem to link libc in the build script for no reason, as well as create a static library for no reason (it doesn't export any functions). As Loris pointed out to me today, this is likely caused by the `zig init` template highlighting static linking rather than modules, which is unfortunate since modules are the preferred way for Zig code to import other Zig code. We'll be adjusting that accordingly.
seems like i created a bit of a fuss there, my bad
Well, that makes me think a lot less of Zig. Bypassing libc makes programs less cooperative members of the broader ecosystem. There's value in libc being able to act as a userspace intermediary between programs and the kernel. (That's why Windows doesn't provide a way to make direct system calls: you're going to go through kernel32/user32/etc. -> ntdll.dll and _then_ the kernel.)
Go bypassing libc causes all sorts of annoying havoc, e.g. fakeroot stuff not working. This is not behavior to be encouraged.
And for what benefit? Being able to say you're libc-free, as if that were a feature not a bug? You don't even have split stacks. Is it just that libc has "c" in the name and you want to make sure nobody thinks you're C? libc being called lib-c is an historical artifact. It's not even about C itself. It's more like Windows ntdll.
Bypassing libc is fundamentally selfish behavior. It breaks a longstanding ecosystem coordination mechanism for zero actual benefit.
But hey, I can still use "zig cc" as a convenient cross-compiler when I'm writing in a better-behaved language -- so thanks, I guess.
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.
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?
You should try it out for yourself first. It could be something weird in my setup since I'm running an immutable distro.
And, no, it doesn't have a Github issue because I didn't feel like tracking down all the moving pieces to replicate it when I can just resolve it with a package install.
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.
No, they should default to the only sane thing left, ignore TERM/termcap/terminfo, and leave the people who still want to run a VT-52 or Tek4014 in the dustbin.
There is a single combination for terminal usage which isn't broken in 2025--ANSI escape codes for output with the Kitty escape codes for input.
(And don't get me started about resolving the dependencies around ncurses, terminfo, etc. because some program is welded to a 15 year old version of curses ...)
What we really need is robust feature detection, not TERM. The terminal universe doesn't seem particularly receptive to the idea: https://github.com/alacritty/vte/issues/98
In the meantime, we have TERM. I'll stop advocating people use TERM when the terminal ecosystem learns what the web had to learn ages ago and embraces feature detection. It's pointless to break even UA detection.
(Unfortunately, as the thread shows, XTGETCAP is broken in too many places to use.)