clix

package module
v2.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 26 Imported by: 5

README ΒΆ

banner

πŸ”— Table of Contents

✨ Features

clix is a kong wrapper, that handles parsing of struct tags, unmarshalling config files, DI, and more. Includes builtin plugins for:

  • Logging (JSON, pretty printed, logging to a file, or discarding) using log/slog.
    • Exposed handler and logger which you can use as a base for any additional logging configuration.
    • Change logging levels easily (and automatically when using --debug).
  • Versioning (--version, --version-json) which aids in printing version information.
    • Exposes Go 1.18's build metadata, the ability to use that as the version info, automatically using VCS information if available.
    • Printing dependencies and build flags.
    • Embedding useful links (support, repo, homepage, etc) in both version output, and help output.
  • Markdown (generate markdown from the CLI's help information). See example 1 and example 2. See below for more details.
  • Built-in --debug flag.
  • Parsing and loading of dotenv files (.env), with dynamic variable expansion.

clix is configurable, so all of the above can be turned on/off, with a reasonable default configuration that should work for most basic apps.

βš™ Usage

go get -u github.com/lrstanley/clix/v2@latest

Example:

package main

import (
	"github.com/lrstanley/clix/v2"
)

type Flags struct {
	Name string `name:"name" default:"world" help:"name to print"`
}

var cli = clix.NewWithDefaults[Flags]()

func main() {
	logger := cli.GetLogger()

	if cli.Debug {
		logger.Debug("thinking really hard...")
	}

	logger.Info("hello", "name", cli.Flags.Name)
}

Example help output

Below shows an example of a non-tagged revision of myproject. When using Git tags, the appropriate tag should be applied as the version, rather than (devel).

$ ./myproject --help
Usage: myproject <command> [flags]

github.com/user/project :: (devel)

    build commit :: 063bc199778e1ae617c35b70608e4ef104d6ce31
      build date :: 2025-10-06T04:35:02Z
      go version :: go1.25.1 linux/amd64

Flags:
  -h, --help            Show context-sensitive help.
  -v, --version         prints version information and exits
      --version-json    prints version information in JSON format and exits
      --name="world"    name to print
  -D, --debug           enables debug mode

Logging flags
  --log.level="info"    logging level (none: disables logging) ($LOG_LEVEL)
  --log.json            output logs in JSON format ($LOG_JSON)
  --log.path=STRING     path to log file (disables stderr logging) ($LOG_PATH)

And version output:

$ ./myproject --version
github.com/user/project :: (devel)
|  build commit :: 063bc199778e1ae617c35b70608e4ef104d6ce31
|    build date :: 2025-10-06T04:35:02Z
|    go version :: go1.25.1 linux/amd64

build options:
|    -buildmode :: exe
|     -compiler :: gc
|   CGO_ENABLED :: 1
|    CGO_CFLAGS ::
|  CGO_CPPFLAGS ::
|  CGO_CXXFLAGS ::
|   CGO_LDFLAGS ::
|        GOARCH :: amd64
|          GOOS :: linux
|       GOAMD64 :: v1
|           vcs :: git
|  vcs.revision :: 063bc199778e1ae617c35b70608e4ef104d6ce31
|      vcs.time :: 2025-10-06T04:35:02Z
|  vcs.modified :: false

dependencies:
  h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0= :: github.com/alecthomas/kong :: v1.12.1
  h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= :: github.com/joho/godotenv :: v1.5.1
  h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= :: github.com/lmittmann/tint :: v1.1.2
                                          unknown :: github.com/lrstanley/clix/v2 :: (devel)
                                          unknown :: github.com/lrstanley/x/logging/handlers :: (devel)

You can also use --version-json for a more programmatic approach to the above information.

Generate Markdown

When using clix, you can generate markdown for your commands by passing the command generate-markdown (hidden when using --help). This will print the markdown to stdout.

./<your-project> generate-markdown > USAGE.md

This functionality is configurable using environment variables:

Environment Variable Description Default
CLIX_TEMPLATE_PATH Path to a directory containing template files to use for the markdown. These inherit from the built-in templates, so you can simply override a specific sub-template to override only a specific section of the markdown. <built-in templates>
CLIX_OUTPUT_PATH Path to write the markdown to, or - to write to stdout. -

See example 1 and example 2 for more examples on what this can look like.

Migrating to v2

v2 is an overhaul of the project, changing the underlying parser, logger, and more. The goal is to use more stdlib functionality where possible (like log/slog), making Markdown generation more flexible, and reduce external dependencies. Should be quite a bit less now!

Start by fetching the new version:

go get -u github.com/lrstanley/clix/v2@latest

High-level changes:

  • Moved from go-flags to kong. Kong is more maintained, powerful, flexible, and has less issues with parse ordering. Kong is also configured through struct tags, though they are slightly different. Some examples of the naming differences:
    • long -> name
    • description -> help
    • env-delim -> sep
    • required:"true" -> required:""
    • optional:"" for optional commands.
    • command -> cmd
    • See the full list here.
  • Kong clears all struct values before parsing, whereas go-flags does not.
  • Kong supports pre-validation hooks, resolvers for loading config files, etc.
  • Kong doesn't create an env var for flags automatically. In v1, this caused a bunch of bugs, as some auto-created env vars would conflict with commonly used env vars. An example of this being --path, which conflicts with the PATH env var used by most operating systems.
  • struct-based flags no longer support --flag foo --flag bar (go-flags style), and now they should be passed in as a single flag, like --flag "foo,bar" with a struct-tag separator defined like sep:"," (the default).
  • Version output no longer includes colors, as this required multiple dependencies. I also don't think it added much value.
  • Logging, versioning, markdown, and Github runner auto-debug mode are now no longer loaded as plugins when using clix.New, but are still loaded by default when using clix.NewWithDefaults.
  • Logging has migrated from github.com/apex/log to log/slog. apex also had pretty bad performance under high load, so you may notice a performance boost.
  • Both prettified and JSON logging fields and output look different. JSON logging uses the standard log/slog JSON handler, with source lines added.
  • Markdown generation now no longer requires "required" flags to be set, which was quite annoying before. It has moved from --generate-markdown (a flag) to generate-markdown (a command).
  • Runner functionality has moved to an external package, github.com/lrstanley/x/scheduler. It now supports crontab-style intervals, and a builder-style interface for creating jobs. See examples/with-scheduler for an example.

See the examples for more details on on what your app should look like.


πŸ™‹β™‚ Support & Assistance

  • ❀ Please review the Code of Conduct for guidelines on ensuring everyone has the best experience interacting with the community.
  • πŸ™‹β™‚ Take a look at the support document on guidelines for tips on how to ask the right questions.
  • 🐞 For all features/bugs/issues/questions/etc, head over here.

🀝 Contributing

  • ❀ Please review the Code of Conduct for guidelines on ensuring everyone has the best experience interacting with the community.
  • πŸ“‹ Please review the contributing doc for submitting issues/a guide on submitting pull requests and helping out.
  • πŸ— For anything security related, please review this repositories security policy.

βš– License

MIT License

Copyright (c) 2021 Liam Stanley <liam@liam.sh>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Also located here

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

This section is empty.

Types ΒΆ

type AppInfo ΒΆ

type AppInfo struct {
	Name        string `json:"name"`          // Application name. Defaults to the main module path.
	Description string `json:"description"`   // Application description.
	Version     string `json:"build_version"` // Build version. Uses VCS info if available.
	Commit      string `json:"build_commit"`  // VCS commit SHA. Uses VCS info if available.
	Date        string `json:"build_date"`    // VCS commit date. Uses VCS info if available.

	Links []Link `json:"links,omitempty"` // Links to the project's website, support, issues, security, etc.
}

func (*AppInfo) Inherit ΒΆ

func (a *AppInfo) Inherit(app AppInfo)

Inherit inherits the values from the provided app.

func (*AppInfo) IsModuleName ΒΆ

func (a *AppInfo) IsModuleName() bool

IsModuleName returns true if the name looks like a module path-style format, otherwise false.

type BuildSetting ΒΆ

type BuildSetting struct {
	// Key and Value describe the build setting.
	// Key must not contain an equals sign, space, tab, or newline.
	// Value must not contain newlines ('\n').
	Key   string `json:"key"`
	Value string `json:"value"`
}

BuildSetting describes a setting that may be used to understand how the binary was built. For example, VCS commit and dirty status is stored here.

func (BuildSetting) String ΒΆ

func (s BuildSetting) String() string

type CLI ΒΆ

type CLI[T any] struct {
	kong.Plugins // Kong-specific plugins.

	// Context is the context returned by kong after initial parsing.
	Context *kong.Context `kong:"-"`

	// Flags are the user-provided flags.
	Flags *T `embed:""`

	// Debug can be used to enable/disable debugging as a global flag. Also
	// sets the log level to debug.
	Debug bool `short:"D" name:"debug" env:"DEBUG" help:"enables debug mode"`
	// contains filtered or unexported fields
}

CLI is the main construct for clix, obtained via New or NewWithDefaults.

func FromContext ΒΆ

func FromContext[T any](ctx context.Context) *CLI[T]

FromContext returns the CLI[T] from the context, or nil if it is not present.

func New ΒΆ

func New[T any](options ...Option[T]) *CLI[T]

New executes the cli parser, with the provided options (no defaults are configured). Order of options is important. See also NewWithDefaults.

Supported struct tags: https://github.com/alecthomas/kong#supported-tags

func NewWithDefaults ΒΆ

func NewWithDefaults[T any](options ...Option[T]) *CLI[T]

NewWithDefaults executes the cli parser, with the provided options and associated recommended defaults. Order of options is important. See also New and Defaults.

Supported struct tags: https://github.com/alecthomas/kong#supported-tags

Initialize a new CLI like so:

type Flags struct {
	// Normal kong flags.
	SomeFlag string `env:"SOME_FLAG" help:"some flag"`
}

var cli = clix.NewWithDefaults[Flags]() // Where Flags is a user-provided type (struct).

func main() {
	// [...]
}

func (*CLI[T]) Execute ΒΆ added in v2.0.6

func (c *CLI[T]) Execute(fn func(ctx context.Context, cli *CLI[T], logger *slog.Logger) error)

Execute is an optional helper for wrapping the body of an application's main() function, so it can exit cleanly. It is only useful as the last call in main(), as it never returns (unless the exit function was overridden via kong.Exit). Usage:

func main() {
	cli.Execute(run)
}

func run(ctx context.Context, cli *clix.CLI[Flags], logger *slog.Logger) error {
	db, err := openDB(ctx)
	if err != nil {
		return err
	}
	defer db.Close() // Runs even if run() returns an error or panics.
	// [...]
}

Behavior:

  • The provided context is canceled when SIGINT or SIGTERM is received (see signal.NotifyContext). After the first signal, default signal handling is restored, so a second signal forcefully terminates the application. The context also contains the CLI (see FromContext).
  • The provided CLI is the CLI that Execute was called on.
  • The provided logger is the logger from CLI.GetLogger, or slog.Default if the logging plugin is disabled.
  • If fn returns nil, or returns context.Canceled after a signal was received, the application exits with code 0.
  • If fn returns an error, it is logged and the application exits with code 1, or the code from the error if it implements kong.ExitCoder.
  • If fn panics, the panic and its stack trace are logged and the application exits with code 2. Note that only panics from the goroutine running fn can be recovered.

Log entries from Execute use the location of the panic as their source, or otherwise the location where Execute was called, rather than Execute itself.

Because fn returns rather than calling os.Exit directly, deferred calls in fn always run before the application exits, unlike with log.Fatal-style helpers.

Alternatively, kong has built-in ways of achieving something similar:

  • Command structs can implement a Run(...) error method, which is invoked through kong.Context.Run (using cli.Context.Run(...)) for the selected command. Its arguments are injected from bound values (see kong.Bind, kong.BindTo, and kong.Context.Bind), and the logger is already bound by the logging plugin. Hooks like AfterApply can also be used to run logic before any Run method.
  • kong.Kong.FatalIfErrorf (using cli.Context.FatalIfErrorf(err)) prints the error to stderr (not through the logger), and exits with code 1, or the code from kong.ExitCoder. Deferred calls are skipped, and panics are not handled.

These can be combined with Execute, by returning the result of cli.Context.Run from fn.

func (*CLI[T]) GenerateMarkdown ΒΆ

func (cli *CLI[T]) GenerateMarkdown() (string, error)

func (*CLI[T]) GetLogHandler ΒΆ

func (c *CLI[T]) GetLogHandler() slog.Handler

GetLogHandler returns the generated logger handler if enabled, nil otherwise. Usually don't need to use this directly. Not used if logging configuration is disabled.

func (*CLI[T]) GetLogger ΒΆ

func (c *CLI[T]) GetLogger() *slog.Logger

GetLogger returns the generated logger if enabled, nil otherwise. If global logging is enabled, you can also use log/slog.Default.

func (*CLI[T]) GetVersion ΒΆ

func (cli *CLI[T]) GetVersion() *Version

GetVersion returns the version information for the CLI, which will be populated after parsing.

func (*CLI[T]) NewContext ΒΆ

func (cli *CLI[T]) NewContext(ctx context.Context) context.Context

NewContext returns a new context with the CLI[T] injected. It can be accessed from the context with FromContext.

func (*CLI[T]) NewHTTPContext ΒΆ

func (cli *CLI[T]) NewHTTPContext(next http.Handler) http.Handler

NewHTTPContext is an http middleware that injects the CLI[T] into the context.

type Link struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

Link allows you to define a link to be included in the version and usage output.

func GithubLinks(repo, branch, homepage string) []Link

GithubLinks return an opinonated set of links for the project, using common Github layout conventions.

type LoggingPlugin ΒΆ

type LoggingPlugin struct {
	// Level is the minimum level of log messages to output, must be one of none|debug|info|warn|error.
	Level string `` /* 127-byte string literal not displayed */

	// JSON enables JSON logging.
	JSON bool `name:"log.json" env:"LOG_JSON" help:"output logs in JSON format"`

	// Path is the path to the log file.
	Path string `name:"log.path" env:"LOG_PATH" type:"path" help:"path to log file (disables stderr logging)"`
}

LoggingPlugin are the flags that define how log entries are processed/returned.

func (*LoggingPlugin) CreateHandler ΒΆ

func (l *LoggingPlugin) CreateHandler(isDebug, setGlobal bool, opts *slog.HandlerOptions) (handler slog.Handler, err error)

CreateHandler creates a new log/slog.Handler with the provided configuration.

func (*LoggingPlugin) GetLevel ΒΆ

func (l *LoggingPlugin) GetLevel() slog.Level

type MarkdownCommand ΒΆ

type MarkdownCommand struct {
	DisableExit bool `kong:"-"`
}

func (*MarkdownCommand) BeforeReset ΒΆ

func (m *MarkdownCommand) BeforeReset(
	ctx *kong.Kong,
	version *Version,
) error

func (*MarkdownCommand) GenerateMarkdown ΒΆ

func (m *MarkdownCommand) GenerateMarkdown(
	model *kong.Application,
	tmpl *template.Template,
	version *Version,
) (string, error)

GenerateMarkdown generates the markdown documentation for the CLI, returning the markdown as a string.

type Module ΒΆ

type Module struct {
	Path    string  `json:"path,omitempty"`     // module path
	Version string  `json:"version,omitempty"`  // module version
	Sum     string  `json:"sum,omitempty"`      // checksum
	Replace *Module `json:"replaces,omitempty"` // replaced by this module
}

Module represents a module.

func (Module) String ΒΆ

func (m Module) String() string

type NonSensitiveVersion ΒΆ

type NonSensitiveVersion struct {
	AppInfo   *AppInfo `json:"app_info,omitempty"` // Application information.
	Command   string   `json:"command"`            // Executable name where the command was called from.
	GoVersion string   `json:"go_version"`         // Version of Go that produced this binary.
	OS        string   `json:"os"`                 // Operating system for this build.
	Arch      string   `json:"arch"`               // CPU Architecture for build build.
}

NonSensitiveVersion represents the version information for the CLI.

type Option ΒΆ

type Option[T any] func(cli *CLI[T])

Option is a function that can be used to configure the CLI.

func Defaults ΒΆ

func Defaults[T any]() []Option[T]

Defaults returns the default options for clix.

func WithAppInfo ΒΆ

func WithAppInfo[T any](app AppInfo) Option[T]

WithAppInfo adds the provided application information to the CLI.

func WithEnvFiles ΒΆ

func WithEnvFiles[T any](paths ...string) Option[T]

WithEnvFiles loads environment variables from ".env" style files from the provided paths. If no paths are provided, it will load from the current working directory as ".env", but will not return an error if the file has access issues/doesn't exist. For variables defined in multiple sources, the precedence is: command-line flags, existing environment variables, then values from the env files. Loading can be disabled with --no-dot-env or NO_DOT_ENV=true.

func WithGithubDebug ΒΆ

func WithGithubDebug[T any]() Option[T]

WithGithubDebug enables debug mode if the RUNNER_DEBUG environment variable is set, which is set when running a Github Action in debug mode.

func WithKongOptions ΒΆ

func WithKongOptions[T any](opts ...kong.Option) Option[T]

WithKongOptions adds the provided kong options to the CLI.

func WithLoggingHandlerOptions ΒΆ

func WithLoggingHandlerOptions[T any](opts *slog.HandlerOptions) Option[T]

WithLoggingHandlerOptions allows customizing how slog handlers are created. This only applies when using the WithLoggingPlugin plugin. By default, the handler options are set to:

  • Level: values passed from --log.level flag.
  • AddSource: true
  • ReplaceAttr: nil

func WithLoggingPlugin ΒΆ

func WithLoggingPlugin[T any](global bool, opts *slog.HandlerOptions) Option[T]

WithLoggingPlugin adds the logging plugin to the CLI. This includes flags for controlling log/slog logging levels, logging to files, JSON output, and supports setting the global slog logger. You can access the resulting log/slog.Handler via CLI.GetLogHandler and the log/slog.Logger via CLI.GetLogger. opts is optional, and can also be set using WithLoggingHandlerOptions.

func WithMarkdownPlugin ΒΆ

func WithMarkdownPlugin[T any]() Option[T]

WithMarkdownPlugin adds a hidden "generate-markdown" command that allows generating markdown documentation for the CLI. To make it so this command ignores any other required flags, it's invoked before kong applies additional restrictions, which means it does not support special flags. To adjust the behavior, you can use environment variables:

  • CLIX_TEMPLATE_PATH: optional path to a directory containing template files to use for the markdown.
  • CLIX_OUTPUT_PATH: path to write the markdown to, or '-' to write to stdout (defaults to stdout).

func WithVersionPlugin ΒΆ

func WithVersionPlugin[T any]() Option[T]

WithVersionPlugin adds the version plugin to the CLI. This includes flags for --version, --version-json, etc.

type Version ΒΆ

type Version struct {
	AppInfo      *AppInfo       `json:"app_info,omitempty"`       // Application information.
	Settings     []BuildSetting `json:"build_settings,omitempty"` // Other information about the build.
	Dependencies []Module       `json:"dependencies,omitempty"`   // Module dependencies.

	Command   string `json:"command"`    // Executable name where the command was called from.
	GoVersion string `json:"go_version"` // Version of Go that produced this binary.
	OS        string `json:"os"`         // Operating system for this build.
	Arch      string `json:"arch"`       // CPU Architecture for build build.
}

Version represents the version information for the CLI.

func GetVersionInfo ΒΆ

func GetVersionInfo(app *AppInfo) *Version

GetVersionInfo returns the version information for the CLI.

func (*Version) GetSetting ΒΆ

func (v *Version) GetSetting(key, defaultValue string) string

GetSetting returns the value of the setting with the given key, otherwise defaults to defaultValue.

func (*Version) NonSensitive ΒΆ

func (v *Version) NonSensitive() *NonSensitiveVersion

NonSensitive returns a copy of Version with sensitive information removed.

func (*Version) String ΒΆ

func (v *Version) String() string

type VersionFlag ΒΆ

type VersionFlag bool

func (VersionFlag) BeforeReset ΒΆ

func (v VersionFlag) BeforeReset(ver *Version) error

type VersionJSONFlag ΒΆ

type VersionJSONFlag bool

func (VersionJSONFlag) BeforeReset ΒΆ

func (v VersionJSONFlag) BeforeReset(ver *Version) error

type VersionPlugin ΒΆ

type VersionPlugin struct {
	Version     VersionFlag     `short:"v" name:"version" help:"prints version information and exits"`
	VersionJSON VersionJSONFlag `name:"version-json" help:"prints version information in JSON format and exits"`
}

Directories ΒΆ

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
⌂ 捒网址