No init functions. No command registration. No flag pointers. Define your CLI in one expression (or just tag a struct)
Inspired by nim's cligen.
Tag a struct, pass a function. Your flags are already in Opts Struct:
type Opts struct {
Count int `cli:"how many times" default:"1"`
Say string `cli:"what to say" default:"hello"`
World bool `cli:"announce it"`
Tags []string `cli:"filter by tags"`
}
func main() {
quicli.RunFunc("say-hello [flags]", "Say Hello to the world", func(o Opts) {
for i := 0; i < o.Count; i++ {
msg := o.Say
if o.World { msg = "π " + msg }
fmt.Println(msg)
}
})
}$ say-hello --help
Say Hello to the world
Usage: say-hello [flags]
--count -c how many times. (default: 1) [env: SAY_HELLO_COUNT]
--say -s what to say. (default: "hello") [env: SAY_HELLO_SAY]
--world -w announce it. (default: false) [env: SAY_HELLO_WORLD]
--tags -t filter by tags. (default: []) [env: SAY_HELLO_TAGS]
Use "say-hello --help" for more information about the command.
$ say-hello --count 3 --say "bonjour"
$ say-hello -w
$ SAY_HELLO_COUNT=5 say-hello # env var, same as --count 5
$ say-hello --completion zsh # print zsh completion scriptSupported field types: int, string, bool, float64, []string.
Tags: cli:"desc" Β· default:"val" Β· short:"x" Β· env:"VAR" (or "-" to opt out).
With subcommands
Give each subcommand its own struct. NewSubcommand infers the flags from it:
type ColorOpts struct {
Foreground bool `cli:"use foreground color"`
}
type WhisperOpts struct {
Say string `cli:"what to whisper" default:"psst"`
Times int `cli:"how many times" default:"1"`
}
func main() {
colorSub := quicli.NewSubcommand("color", "print in red", func(o ColorOpts) {
fmt.Println("foreground:", o.Foreground)
})
colorSub.Aliases = quicli.Aliases("co") // optional
quicli.Cli{
Usage: "say-hello [command] [flags]",
Description: "Say Hello to the world",
Function: func(cfg quicli.Config) { fmt.Println("hello") },
Subcommands: quicli.Subcommands{
colorSub,
quicli.NewSubcommand("whisper", "say quietly", func(o WhisperOpts) {
for i := 0; i < o.Times; i++ { fmt.Println(o.Say) }
}),
},
}.RunWithSubcommand()
}$ say-hello color --foreground
$ say-hello co --foreground # alias works
$ say-hello whisper --say "shhh" --times 2Everything in one expression:
quicli.Run(quicli.Cli{
Usage: "say-hello [flags]",
Description: "Say Hello to the world",
Flags: quicli.Flags{
{Name: "count", Default: 1, Description: "how many times"},
{Name: "say", Default: "hello", Description: "what to say"},
{Name: "world", Description: "announce it"},
},
Function: func(cfg quicli.Config) {
count := cfg.GetIntFlag("count")
say := cfg.GetStringFlag("say")
world := cfg.GetBoolFlag("world")
for i := 0; i < count; i++ {
if world { fmt.Print("π ") }
fmt.Println(say)
}
},
})With subcommands
quicli.Cli{
Usage: "mytool [command] [flags]",
Description: "A tool that does things",
Flags: quicli.Flags{
{Name: "verbose", Description: "verbose output"},
{Name: "output", Default: "text", Description: "output format",
SharedSubcommand: quicli.SubcommandSet{"get", "list"}},
},
Function: Root,
Subcommands: quicli.Subcommands{
{Name: "get", Aliases: quicli.Aliases("g"), Description: "get a resource", Function: Get,
Flags: quicli.Flags{{Name: "id", Default: "", Description: "resource id"}}},
{Name: "list", Aliases: quicli.Aliases("ls"), Description: "list resources", Function: List},
{Name: "delete", Description: "delete a resource", Function: Delete},
},
}.RunWithSubcommand()$ mytool --help
A tool that does things
Usage: mytool [command] [flags]
Available commands: get, g, list, ls, delete
--verbose -v verbose output. (default: false) [env: MYTOOL_VERBOSE]
Use "mytool --help" for more information about the command.
$ mytool get --help
Command get: get a resource
--id -i resource id. (default: "") [env: MYTOOL_ID]
--output -o output format. (default: "text") [env: MYTOOL_OUTPUT]
--verbose -v verbose output. (default: false) [env: MYTOOL_VERBOSE]
$ mytool get --id abc123
$ mytool g --id abc123 # alias works
$ mytool lst # quicli error: unknown subcommand 'lst', did you mean 'list'?
$ MYTOOL_VERBOSE=true mytool listEvery quicli CLI gets these for free β no configuration needed:
Env var fallback β PROGNAME_FLAGNAME is checked before the default. Shown in help.
SAY_HELLO_COUNT=10 ./say-hello # same as --count 10Override per flag: EnvVar: "MY_CUSTOM_VAR" Β· Opt out: EnvVar: "-"
Short flags β first letter auto-derived (--count β -c). Override with ShortName: "n".
Shell completion β one flag, three shells:
./say-hello --completion bash >> ~/.bash_completion
./say-hello --completion zsh > ~/.zsh/completions/_say-hello
./say-hello --completion fish > ~/.config/fish/completions/say-hello.fishTypo detection β suggests the closest subcommand on misspelling:
$ mytool delet
quicli error: unknown subcommand 'delet', did you mean 'delete'?
Get more examples
quicli is a thin wrapper around Go's
flagpackage. Use it to write CLIs fast, not to build complex command hierarchies.