clix

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 11 Imported by: 6

README

Clix

CLI convenience module for Frostyard tools.

Wraps charmbracelet/fang and spf13/cobra to provide standardized version strings, common flags, JSON output helpers, and a reporter factory. Consuming CLIs only need to define their own commands.

Install

go get github.com/frostyard/clix

Usage

package main

import (
	"fmt"

	"github.com/frostyard/clix"
	"github.com/spf13/cobra"
)

// Set via ldflags at build time.
var (
	version = "dev"
	commit  = "none"
	date    = "unknown"
	builtBy = "local"
)

func main() {
	app := clix.App{
		Version: version,
		Commit:  commit,
		Date:    date,
		BuiltBy: builtBy,
	}

	rootCmd := &cobra.Command{
		Use:   "mytool",
		Short: "An example CLI built with clix",
		RunE: func(cmd *cobra.Command, args []string) error {
			// Use the reporter for progress output
			r := clix.NewReporter()
			r.Message("doing work...")

			result := map[string]string{"status": "ok"}

			// OutputJSON writes to stdout when --json is set
			if written, err := clix.OutputJSON(result); written {
				return err
			}

			// Fall through to text output
			fmt.Println("done")
			return nil
		},
	}

	if err := app.Run(rootCmd); err != nil {
		clix.OutputJSONError("command failed", err)
	}
}

Build with ldflags for version injection:

go build -ldflags "-X main.version=1.0.0 -X main.commit=$(git rev-parse HEAD) -X main.date=$(date -I) -X main.builtBy=ci"

Flags

App.Run() automatically registers these persistent flags on the root command:

Flag Short Description
--json Output in JSON format
--verbose -v Verbose output
--dry-run -n Dry run mode (no actual changes)
--silent -s Suppress all progress output

Flag values are available as package-level variables: clix.JSONOutput, clix.Verbose, clix.DryRun, clix.Silent.

Reporter

clix.NewReporter() returns a reporter based on the active flags:

Priority Condition Reporter Output
1 (highest) --silent NoopReporter none
2 --json JSONReporter stdout
3 (default) neither TextReporter stderr

Silent always takes priority over JSON. Text output goes to stderr to keep stdout clean for data.

Testing output

Every clix output path — OutputJSON, OutputJSONError, and the reporters from NewReporter — writes through two package-level writer seams, clix.Stdout and clix.Stderr. Both default to nil, meaning "the current os.Stdout / os.Stderr at call time", so production behavior is unchanged and consumers that already swap os.Stdout in tests keep working. Stdout / Stderr are shared package-level variables, so tests that set them should not run in parallel unless they coordinate access. In tests, assign a bytes.Buffer instead of touching the process globals: assign a bytes.Buffer instead of touching the process globals:

func TestStatusJSON(t *testing.T) {
	var out bytes.Buffer
	clix.Stdout = &out
	defer func() { clix.Stdout = nil }()
	clix.JSONOutput = true
	defer func() { clix.JSONOutput = false }()

	// ... run the command ...

	var got map[string]any
	if err := json.Unmarshal(out.Bytes(), &got); err != nil {
		t.Fatal(err)
	}
}

Viper Integration

If your CLI uses spf13/viper for config, bind the common flags in PersistentPreRunE:

rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
	return clix.BindViper(cmd)
}

This binds --json, --verbose, --dry-run, and --silent to viper keys so they can be set via config files or environment variables.

Development

make test            # run all tests
make lint            # run golangci-lint
make check           # fmt + lint + test (pre-commit gate)
make bump            # tag next semver with svu and push; the tag push publishes the GitHub release

The e2e suite in tests/e2e/ builds a consumer program wired like the example above and runs it as a subprocess. Contributing guide: AGENTS.md (CONTRIBUTING.md resolves to it).

Documentation

Overview

Package clix provides CLI convenience functions for Frostyard tools, wrapping charmbracelet/fang and spf13/cobra with standardized version injection, common flags, JSON output helpers, and reporter factory.

Index

Constants

This section is empty.

Variables

View Source
var (
	JSONOutput bool // --json flag value
	Verbose    bool // --verbose / -v flag value
	DryRun     bool // --dry-run / -n flag value
	Silent     bool // --silent / -s flag value
)

Common flag values, populated when Run() registers persistent flags.

View Source
var Stderr io.Writer

Stderr, when non-nil, replaces the process's standard error for the text reporter returned by NewReporter. Same contract as Stdout: nil means "use the current os.Stderr at call time".

View Source
var Stdout io.Writer

Stdout, when non-nil, replaces the process's standard output for every clix output path (OutputJSON, OutputJSONError, and the JSON reporter from NewReporter). It exists so tests — in clix and in consuming CLIs — can capture output with a bytes.Buffer instead of swapping os.Stdout. It defaults to nil, which means "use the current os.Stdout at call time", so consumers that already swap os.Stdout keep working unchanged.

Functions

func BindViper

func BindViper(cmd *cobra.Command) error

BindViper binds the common flags (--json, --verbose, --dry-run, --silent) to viper. Call this in a PersistentPreRunE if your app uses viper for config management.

func NewReporter

func NewReporter() reporter.Reporter

NewReporter returns the appropriate reporter based on the Silent and JSONOutput flags. Silent mode: NoopReporter (suppresses all output). Silent takes priority over JSON. JSON mode: JSONReporter writing to stdout (for piping/parsing). Text mode: TextReporter writing to stderr (keeps stdout clean for data). The writers are resolved through Stdout / Stderr when set, otherwise the current os.Stdout / os.Stderr.

func OutputJSON

func OutputJSON(data any) (bool, error)

OutputJSON writes data as indented JSON to stdout if JSONOutput is true. Returns true if output was written, false if JSON mode is not active. If encoding fails, a fallback error envelope is written to stdout and the encoding error is returned alongside true (output was still written). Output goes to Stdout when set, otherwise to os.Stdout.

func OutputJSONError

func OutputJSONError(message string, err error) error

OutputJSONError writes a structured error object as JSON to stdout and returns a wrapped error for the caller to propagate. If err is nil, the message alone is used as the error text.

Types

type App

type App struct {
	Version string
	Commit  string
	Date    string
	BuiltBy string
}

App holds build-time metadata for a CLI application. Create one in main() and call Run() to execute the root command.

func (*App) Run

func (a *App) Run(cmd *cobra.Command) error

Run registers common persistent flags on cmd, then executes the command via fang.Execute with the formatted version string and signal handling.

func (*App) VersionString

func (a *App) VersionString() string

VersionString returns a formatted version string including commit, date, and builder info. Example: "1.2.3 (Commit: abc) (Date: 2026-01-01) (Built by: ci)"

Directories

Path Synopsis
tests
e2e/exampletool command
Command exampletool is the consumer program clix's end-to-end suite builds and runs as a subprocess.
Command exampletool is the consumer program clix's end-to-end suite builds and runs as a subprocess.

Jump to

Keyboard shortcuts

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