Changelog
June 23, 2026 · View on GitHub
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to semantic-ish versioning.
[Unreleased]
Changed
- Duplicate-bind panics now include the command path. When an option (or
argument) is bound twice on the same command, the panic message now appends
in command '<path>'(e.g.redefined option flag 'dry-run' in command 'git branch'), making the offending command easy to locate. The command's flag set is now named by its full path (Command.Path()); the flag-set name is only used for such diagnostics, so help output is unaffected.
v3.8.0 - 2026-06-22
⚠️ Breaking Changes
- Per-app parse state moved out of
GlobalOptsinto a newAppOptionstype. The runtime fieldsShowHelp/ShowVersion/inCompletion/genCompletionare no longer onGlobalOpts; eachApp(and standalone command) now owns its ownAppOptions, so concurrentAppinstances no longer share these.App.Opts()still returns the process-level*GlobalOpts(soapp.Opts() == gcli.GOpts()andapp.Opts().Verboseare unchanged); use the newApp.AppOpts()for the per-app state. Process-level config (Verbose/ strict /EnhanceShortand the logger) stays in the package singleton, so log-level behavior is unaffected.
Added
- Three-tier option model with shared (inherited) options:
Command.SharedOpts()(≈ cobra'sPersistentFlags). Options bound onc.SharedOpts()are inherited by the command and all of its descendant commands, sharing the same bound variable (the sameflag.Value/pointer). This adds a shared middle tier between the existing global (app) and local (per-command) options, so a parent option like--git-dircan be written and parsed in any sub-command segment —app top sub --git-dir /xand (with arg reorder)app top sub arg --git-dir /xboth work. Usec.SharedOpts()with any binder (BoolOpt/StrOpt/Opt[T]/FromStruct/...). A local option of the same name on a sub-command takes priority;Requiredon a shared option is validated at the executing (leaf) command. New gflag primitiveParser.InheritOptsFrom(src, category...)re-registers another parser's options by their underlyingflag.Value. In a sub-command's help, options inherited from ancestors are grouped under anInherited Optionssection (a command's own shared options render with its local options). - Command documentation generation: new
docgenpackage + builtinGenDoccommand (≈ cobra'scobra/doc).docgenrenders a single command or a whole app to markdown (CmdMarkdown/AppMarkdown/MarkdownTree) and man pages (roff) (CmdMan/ManTree). Addbuiltin.GenDoc()to an app, then run./cliapp gendoc -f md|man -o ./docsto export docs. Adds agflag.CliOpt.TypeName()accessor for the option type name.
Fixed
FromStructnow expands anonymous embedded structs of an unexported type. The unexported-field-name skip used to run before the anonymous-field check, so an embedded field whose type name is lowercase (e.g.baseFlags,commonOpts— as used by thestruct-flagdemo and the docs) was silently dropped and its options were never generated. The anonymous check now runs first; the exported inner fields of such an embed are reflectable/addressable and bind correctly. This is independent of the tag rule (TagRuleNamed/TagRuleSimple/TagRuleField).
v3.7.0 - 2026-06-22
Added
- Struct-tag binding: more field types +
enum.FromStructnow binds native[]string/[]int/[]bool(repeatable, e.g.--name a --name b),time.Duration(e.g.--ttl 1h30m), andmap[string]string(repeatable--meta k=v) directly — no need to declare the specialgflag.Strings/KVStringtypes. A newenum:"a,b,c"tag key sets the option's value candidates (for completion) and adds membership validation. Internally the struct binder no longer usesunsafe. - Generic option binding:
gflag.Opt[T]/gflag.BindVar[T]. A type-safe generic API that dispatches on the pointer type to the matching binder, so one call replaces the per-typeBoolVar/IntVar/StrVar/.... Supports the same set of types as struct binding (scalars,time.Duration, slices,map[string]string, and anyflag.Value). Example:gflag.Opt(fs, &name, "name", "n", "tom", "user name").
v3.6.0 - 2026-06-21
⚠️ Breaking Changes
- Renamed package
github.com/gookit/gcli/v3/events→github.com/gookit/gcli/v3/gevent. The directory, file, and package name were renamed for naming consistency with the other sub-packages (e.g.gflag). The oldeventspackage no longer exists; update your imports. The event-name constants themselves are unchanged (OnAppInitAfter,OnCmdRunBefore, ...).
Added
- Full event-name aliases on the
gclipackage. Every event name is now exposed as agcli.Evt*constant (1:1 with thegevent.On*names), so you can reference event names directly fromgcliwithout importing the event package. New aliases:EvtAppInitBefore,EvtAppExit,EvtAppBindOptsBefore,EvtAppBindOptsAfter,EvtAppCmdAdd,EvtAppCmdAdded,EvtAppOptsParsed,EvtAppHelpBefore,EvtAppHelpAfter,EvtCmdInitBefore. - Auto-reorder of input args (
Config.DisableReorderArgs, enabled by default). Before parsing options, the input args are rearranged into the canonical--options... argumentsform, so options written after positional arguments are still parsed instead of being silently dropped — e.g.cmd arg --name tomnow works the same ascmd --name tom arg. A known value-taking option keeps its value (--name tom); bool options,--opt=val, negative-number tokens (-5), a lone-, and everything after--are handled correctly. In a multi-level app only the final executed command's args are reordered — reordering stops at a sub-command name, so parent/sub option sets never mix. Disable per parser viagflag.WithReorderArgs(false)orConfig.DisableReorderArgs = trueto restore the strict std-flag order.
Changed
- Mixed
arguments+--optionsinput on a command no longer loses the options. This is a behavior change but strictly more permissive: any input that parsed before still parses the same; only previously-failing orders now succeed.
Migration
| Before | After |
|---|---|
import "github.com/gookit/gcli/v3/events" | import "github.com/gookit/gcli/v3/gevent" |
events.OnAppInitAfter | gevent.OnAppInitAfter — or gcli.EvtAppInit (no import needed) |
events.OnCmdRunBefore | gevent.OnCmdRunBefore — or gcli.EvtCmdRunBefore (no import needed) |
Tip: prefer the
gcli.Evt*aliases to drop the event-package import entirely.
v3.5.0 - 2026-06-06
Highlights: more flexible struct binding (a new field tag rule plus automatic anonymous-field expansion), declarative interactive input via Question, and opt-in POSIX short-option merging through EnhanceShort — configurable per command or globally with gcli.SetEnhanceShort().
Added
- Struct binding:
TagRuleFieldtag rule. A new rule forFromStructthat uses the field name (SnakeCase) as the option name and reads metadata from independent tag keys (flagfor shorts, plusdesc/default/required). Select it viac.FromStruct(ptr, gcli.TagRuleField). - Struct binding: anonymous field expansion. Anonymous nested structs are now expanded automatically, so a shared option set can be embedded and reused.
- Declarative interactive input:
CliOpt.Question. When an option value is empty, GCli can collect it via an interactive prompt (a built-in default collector). Set it withgflag.WithQuestion("..."). A customCollectorstill takes priority overQuestion. - POSIX short-option enhancement:
Config.EnhanceShort. Opt-in combining of short options with self-documenting levelsEnhanceShortNone(0, default),EnhanceShortMerge(1,-aux=>-a -u -xwhen all are bool), andEnhanceShortAttach(2, also-Ostdout=>-O stdout). A group is split only when all members are bool short options, so value-taking shorts are never mis-parsed. - Global
EnhanceShortsetting.gcli.SetEnhanceShort(level)/gcli.EnhanceShort()apply a level to every command at once; a command's ownConfig.EnhanceShortstill takes priority. - Demo commands under
_examples/cmd:struct-flag(field tag + anonymous),short-merge(EnhanceShort),ask-demo(Question).
Changed
- Strict mode now drives the safe
EnhanceShortpath internally instead of the old "blind split", which used to mis-split value-taking short options (e.g.-Ostdout).strictFormatArgsis reduced to long-option normalization only.
v3.4.1 - 2026-06-05
Added
- Built-in shell completion (no
genacregistration needed).--gen-completion <bash|zsh|pwsh>statically generates a completion script and exits.--in-completioncomputes completion candidates at runtime; generated scripts are thin and delegate to it, so they need no regeneration when commands change.- PowerShell (pwsh) dynamic completion via
Register-ArgumentCompleter. - A silent completion mode suppresses lifecycle hooks so stdout only contains candidates / the script.
- Option value candidates:
CliOpt.Choices(gflag.WithChoices(...)) feed value completion for an option. - Command middleware:
Command.Use(...)runs handlers in registration order before the command's mainFunc; any handler returning an error aborts the chain. - Application middleware:
App.Use(...)applies before every command.
Fixed
doExecute'srecover()is now a properdefer, so panics during command execution are actually caught.
v3.4.0 - 2026-06-04
⚠️ Breaking Changes
- Removed public package
github.com/gookit/gcli/v3/helper. It has been moved to the internal packageinternal/helper. These were internal-only utilities (IsGoodName,IsGoodCmdId,IsGoodCmdName,Panicf,RenderText). If you imported it directly, inline your own helpers instead. - Removed unused public package
github.com/gookit/gcli/v3/gclicom(it was a leftover after the show/progress migration togookit/cliuiand had no in-tree usages). - Removed the global
--verbose/--verboption. It bound to a per-app copy that was never read by the logger, so it had no effect. Control the log level via the env varGCLI_VERBOSE(e.g.GCLI_VERBOSE=debug) orgcli.SetVerbose()/gcli.SetDebugMode()in code.
Added
- Command grouping in help. Set
Command.Categoryto group commands under a titled section in the application help. Uncategorized commands fall back to the defaultAvailable Commandsgroup (output unchanged when no category is used). - Option grouping in help. Set
CliOpt.Category(or usegflag.WithCategory("name")) to group options under a titled sub-section. Uncategorized options render first with no sub-title (backward compatible).
Fixed
help <command>now works on first invocation. Previously it printedunknown input command "help"becausehelpwas not treated as a command.findSimilarCmdno longer pollutes the command registry. It used to write a phantomhelpentry into the realcmdNamesmap on any unknown-command run.Command.Copy()no longer clears the source command's hooks (the shallow copy shared the*Hookspointer and reset the original).gflag.Parser.Parseno longer silently swallows panics — a recovered panic is now returned as an error instead of being printed and ignored.- Fixed a compile error in the
_examplesprogress demo after thecliui/progressint64signature change.
Changed (internal)
- Global options are now a single source of truth:
Appreuses the package-levelgOpts. NOTE: multipleAppinstances in the same process now share the global options (verbose/help/version/strict/completion). App.findCommandNameis now side-effect free (returns afoundCmdinstead of mutatingapp.args/app.inputNamemid-parse).- Merged the duplicated option-validation logic shared by
Parser.ParseandCliOpts.ParseOptsinto a singlevalidateAll. - Moved internal-only
helperutilities underinternal/.
Migration
| Before | After |
|---|---|
import "github.com/gookit/gcli/v3/helper" | internal now — inline your own helper |
import "github.com/gookit/gcli/v3/gclicom" | removed |
CLI flag --verbose 4 | env GCLI_VERBOSE=debug or gcli.SetVerbose(gcli.VerbDebug) |
v3.0.1 - 2021-04-23
new
- add some special flag type vars
- support hidden command on render help by
c.Hidden=true
fixed
- alias not works on command ID
- render color on command/option/argument description
v3.0.0 - 2021-04-23
new
- support multi level sub commands
- support parse flags from struct tags
- support flag/argument validate
- support controller on application
app.controllers []Controller- 独立于commands之外的。Independent of commands.
- 支持组选项,全部子命令都拥有这些选项
Config/GroupOptions()里绑定组选项。
- 支持单个command、controller独立运行