clix

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 25, 2025 License: MIT Imports: 12 Imported by: 0

README

clix

  ██████╗ ██╗      ██╗ ██╗  ██╗
 ██╔════╝ ██║      ██║ ╚██╗██╔╝
 ██║      ██║      ██║  ╚███╔╝
 ██║      ██║      ██║  ██╔██╗
 ╚██████╗ ███████╗ ██║ ██╔╝ ██╗
  ╚═════╝ ╚══════╝ ╚═╝ ╚═╝  ╚═╝

Introduction

clix is an opinionated, batteries-optional framework for building nested CLI applications using plain Go. It provides a declarative API for describing commands, flags, and arguments while handling configuration hydration, interactive prompting, and contextual execution hooks for you. The generated Go reference documentation lives on pkg.go.dev, which always reflects the latest published API surface.

Inspired by

clix would not exist without the great work done in other CLI ecosystems. If you need a different mental model or additional batteries, definitely explore:

clix is designed to be simple by default, powerful when needed—starting with core functionality and allowing optional features through an extension system.

Principles

clix follows a few core behavioral principles that ensure consistent and intuitive CLI interactions:

  1. Groups show help: Commands with children but no Run handler (groups) display their help surface when invoked, showing available groups and commands.

  2. Commands with handlers execute: Commands with Run handlers execute when called. If they have children, the handler executes when called without arguments, or routes to child commands when a child name is provided.

  3. Actionable commands prompt: Commands without children that require arguments will automatically prompt for missing required arguments, providing a smooth interactive experience.

  4. Help flags take precedence: Global and command-level -h/--help flags always show help information, even if arguments are missing.

  5. Configuration precedence: Values are resolved in the following order (highest precedence first): Command flags > App flags > Environment variables > Config file > Defaults

    • Command-level flag values (flags defined on the specific command)
    • App-level flag values (flags defined on the root command, accessible via app.Flags())
    • Environment variables matching the flag's EnvVar or the default pattern APP_KEY
    • Entries in ~/.config/<app>/config.yaml
    • Flag defaults defined on the command or app flag set

Goals

  • Minimal overhead for simple apps: Core library is lightweight, with optional features via extensions
  • Declarative API: Describe your CLI structure clearly and concisely
  • Consistent behavior: Predictable help, prompting, and command execution patterns
  • Great developer experience: Clear types, helpful defaults, and comprehensive examples
  • Batteries-included when needed: Extensions provide powerful features without polluting core functionality
  • Structured output support: Built-in JSON/YAML/text formatting for machine-readable output
  • Interactive by default: Smart prompting that guides users through required inputs

Not Goals

  • Everything as a dependency: Features like help commands, autocomplete, and version checking are opt-in via extensions
  • Complex state management: clix focuses on CLI structure, not application state
  • Built-in templating or rich output: While styling is supported, clix doesn't include markdown rendering or complex UI components by default

When to Use clix

clix is a good fit if you want:

  • A strict tree of groups and commands – Clear hierarchy where groups organize commands and commands execute handlers
  • Built-in prompting and config precedence – Automatic prompting for missing arguments and consistent flag/env/config/default resolution
  • Extensions instead of code generation – Optional features via extensions rather than codegen tools
  • Declarative API – Describe your CLI structure clearly with structs, functional options, or builder-style APIs

If you need code generation, complex plugin systems, or a more flexible command model, consider Cobra or ff.

  • Command auto-generation: You explicitly define your command tree
  • Automatic flag inference: Flags must be explicitly declared (though defaults, env vars, and config files reduce boilerplate)

Quick Start

Applications built with clix work best when the executable wiring and the command implementations live in separate packages. A minimal layout looks like:

demo/
  cmd/demo/main.go
  cmd/demo/app.go
  internal/greet/command.go

cmd/demo/main.go bootstraps cancellation, logging, and error handling for the process:

// cmd/demo/main.go
package main

import (
        "context"
        "fmt"
        "os"
)

func main() {
        app := newApp()

        if err := app.Run(context.Background(), nil); err != nil {
                fmt.Fprintln(app.Err, err)
                os.Exit(1)
        }
}

cmd/demo/app.go owns the clix.App and root command definition while delegating child commands to the internal/ tree:

// cmd/demo/app.go
package main

import (
        "clix"
        "clix/ext/help"
        "example.com/demo/internal/greet"
)

func newApp() *clix.App {
        app := clix.NewApp("demo")
        app.Description = "Demonstrates the clix CLI framework"

        var project string
        app.Flags().StringVar(clix.StringVarOptions{
                FlagOptions: clix.FlagOptions{
                        Name:   "project",
                        Usage:  "Project to operate on",
                        EnvVar: "DEMO_PROJECT",
                },
                Value:   &project,
                Default: "sample-project",
        })

        root := clix.NewCommand("demo")
        root.Short = "Root of the demo application"
        root.Run = func(ctx *clix.Context) error {
                return clix.HelpRenderer{App: ctx.App, Command: ctx.Command}.Render(ctx.App.Out)
        }
        root.Children = []*clix.Command{
                greet.NewCommand(&project),
        }

        app.Root = root

        // Add optional extensions
        app.AddExtension(help.Extension{})

        return app
}

The implementation of the greet command (including flags, arguments, and handlers) lives in internal/greet:

// internal/greet/command.go
package greet

import (
        "fmt"

        "clix"
)

func NewCommand(project *string) *clix.Command {
        cmd := clix.NewCommand("greet")
        cmd.Short = "Print a friendly greeting"
        cmd.Arguments = []*clix.Argument{{
                Name:     "name",
                Required: true,
                Prompt:   "Name of the person to greet",
        }}
        cmd.PreRun = func(ctx *clix.Context) error {
                fmt.Fprintf(ctx.App.Out, "Using project %s\n", *project)
                return nil
        }
        cmd.Run = func(ctx *clix.Context) error {
                fmt.Fprintf(ctx.App.Out, "Hello %s!\n", ctx.Args[0])
                return nil
        }
        cmd.PostRun = func(ctx *clix.Context) error {
                fmt.Fprintln(ctx.App.Out, "Done!")
                return nil
        }
        return cmd
}

When no positional arguments are provided, clix will prompt the user for any required values. For example demo greet will prompt for the name argument before executing the command handler. Because the root command's Run handler renders the help surface, invoking demo on its own prints the full set of available commands.

The full runnable version of this example (including additional flags and configuration usage) can be found in examples/basic.

Features

Hierarchical Commands

Commands can contain nested children (groups or commands), forming a tree structure. clix distinguishes between:

  • Groups: Commands with children but no Run handler (interior nodes that show help)
  • Commands: Commands with Run handlers (executable, may have children or be leaf nodes)

Each command supports:

  • Aliases: Alternative names for the same command
  • Usage metadata: Short descriptions, long descriptions, examples
  • Visibility controls: Hidden commands for internal or experimental features
  • Execution hooks: PreRun, Run, and PostRun handlers

Creating groups and commands:

// Create a group (organizes child commands, shows help when called)
users := clix.NewGroup("users", "Manage user accounts",
        clix.NewCommand("create"), // child command
        clix.NewCommand("list"),   // child command
)

// Or create a command with both handler and children
auth := clix.NewCommand("auth")
auth.Short = "Authentication commands"
auth.Run = func(ctx *clix.Context) error {
        fmt.Println("Auth handler executed!")
        return nil
}
auth.AddCommand(clix.NewCommand("login"))
auth.AddCommand(clix.NewCommand("logout"))

// Groups show help, commands with handlers execute
// cli users        -> shows help
// cli auth         -> executes auth handler
// cli auth login   -> executes login child
Flags and Configuration

Global and command-level flags support:

  • Environment variable defaults: Automatically read from environment
  • Config file defaults: Persistent configuration in ~/.config/<app>/config.yaml
  • Flag variants: Long (--flag), short (-f), with equals (--flag=value) or space (--flag value)
  • Type support: String, bool, int, int64, float64
  • Precedence: Command flags > App flags > Environment variables > Config file > Defaults
var project string
app.Flags().StringVar(clix.StringVarOptions{
        FlagOptions: clix.FlagOptions{
                Name:   "project",
                Short:  "p",
                Usage:  "Project to operate on",
                EnvVar: "MYAPP_PROJECT",
        },
        Value:   &project,
        Default: "default-project",
})
Typed configuration access (optional schema)

When you read persisted configuration directly, you can use typed helpers:

if retries, ok := app.Config.Integer("project.retries"); ok {
        fmt.Fprintf(app.Out, "Retry count: %d\n", retries)
}
if enabled, ok := app.Config.Bool("feature.enabled"); ok && enabled {
        fmt.Fprintln(app.Out, "Feature flag is on")
}

If you want cli config set to enforce types, register an optional schema:

app.Config.RegisterSchema(
        clix.ConfigSchema{
                Key:  "project.retries",
                Type: clix.ConfigInteger,
                Validate: func(value string) error {
                        if value == "0" {
                                return fmt.Errorf("retries must be positive")
                        }
                        return nil
                },
        },
        clix.ConfigSchema{
                Key:  "feature.enabled",
                Type: clix.ConfigBool,
        },
)

With a schema in place, cli config set project.retries 10 is accepted, while non-integer input is rejected with a clear error. Schemas are optional—keys without entries continue to behave like raw strings.

Positional Arguments

Commands can define required or optional positional arguments with:

  • Automatic prompting: Missing required arguments trigger interactive prompts
  • Validation: Custom validation functions run before execution
  • Default values: Optional arguments can have defaults
  • Smart labels: Prompt labels default to title-cased argument names
cmd.Arguments = []*clix.Argument{{
        Name:     "email",
        Prompt:   "Email address",
        Required: true,
        Validate: func(value string) error {
                if !strings.Contains(value, "@") {
                        return fmt.Errorf("invalid email")
                }
                return nil
        },
}}
Interactive Prompting

clix provides several prompt types:

  • Text input: Standard text prompts with validation
  • Select: Navigable single-selection lists (requires prompt extension)
  • Multi-select: Multiple selection lists (requires prompt extension)
  • Confirm: Yes/no confirmation prompts

Prompts automatically use raw terminal mode when available (for arrow key navigation) and fall back to line-based input otherwise.

The prompt API supports both struct-based and functional options patterns. The struct-based API (using PromptRequest) is the primary API and is consistent with the rest of the codebase.

Struct-based API (recommended):

// Basic text prompt
result, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
        Label:   "Enter name",
        Default: "unknown",
})

// Select prompt
result, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
        Label: "Choose an option",
        Options: []clix.SelectOption{
                {Label: "Option A", Value: "a"},
                {Label: "Option B", Value: "b"},
        },
})

// Confirm prompt
result, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
        Label:   "Continue?",
        Confirm: true,
})

Functional options API:

// Basic text prompt
result, err := ctx.App.Prompter.Prompt(ctx,
        clix.WithLabel("Enter name"),
        clix.WithDefault("unknown"),
)

// Select prompt (requires prompt extension)
import "clix/ext/prompt"
result, err := ctx.App.Prompter.Prompt(ctx,
        clix.WithLabel("Choose an option"),
        prompt.Select([]clix.SelectOption{
                {Label: "Option A", Value: "a"},
                {Label: "Option B", Value: "b"},
        }),
)

// Confirm prompt
result, err := ctx.App.Prompter.Prompt(ctx,
        clix.WithLabel("Continue?"),
        clix.WithConfirm(),
)

Both APIs can be mixed - functional options can be combined with PromptRequest structs, with later options overriding earlier values.

Structured Output

Global --format flag supports json, yaml, and text output formats:

// In your command handler
return ctx.App.FormatOutput(data) // Uses --format flag automatically

Commands like version and config list automatically support structured output for machine-readable workflows.

Styling

Optional styling hooks allow integration with packages like lipgloss:

import "github.com/charmbracelet/lipgloss"

style := lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
app.DefaultTheme.PrefixStyle = clix.StyleFunc(style.Render)
app.Styles.SectionHeading = clix.StyleFunc(style.Render)
// Style app-level vs. command-level flags differently if desired
app.Styles.AppFlagName = clix.StyleFunc(style.Render)
app.Styles.CommandFlagName = clix.StyleFunc(style.Render)

Key style hooks:

  • AppFlagName / AppFlagUsage – app-level flags defined on app.Flags()
  • CommandFlagName / CommandFlagUsage – command-level flags defined on cmd.Flags()
  • FlagName / FlagUsage – base styles applied when the more specific hooks are unset
  • ChildName / ChildDesc – section entries under GROUPS and COMMANDS

Styling is optional—applications without styling still work perfectly.

Command Context

Command handlers receive a *clix.Context that embeds context.Context and provides:

  • Access to the active command and arguments
  • Application instance and configuration
  • Hydrated flag/config values via type-specific getters: String(), Bool(), Integer(), Int64(), Float64()
  • Argument access: Arg(index), ArgNamed(name), AllArgs()
  • Standard output/error streams
  • Standard context.Context functionality (cancellation, deadlines, values)

All getter methods follow the same precedence: command flags > app flags > env > config > defaults

Context Layering:

  • App.Run(ctx context.Context, ...) accepts a standard context.Context for process-level cancellation and deadlines
  • For each command execution, clix builds a *clix.Context that embeds the original context.Context and adds CLI-specific data
  • Within handlers, pass *clix.Context directly to functions that accept context.Context (like Prompter.Prompt) - no need to use ctx.Context
cmd.Run = func(ctx *clix.Context) error {
        // Access CLI-specific data via type-specific getters
        // All methods follow the same precedence: command flags > app flags > env > config > defaults
        if project, ok := ctx.String("project"); ok {
                fmt.Fprintf(ctx.App.Out, "Using project %s\n", project)
        }
        
        if verbose, ok := ctx.Bool("verbose"); ok && verbose {
                fmt.Fprintf(ctx.App.Out, "Verbose mode enabled\n")
        }
        
        if port, ok := ctx.Integer("port"); ok {
                fmt.Fprintf(ctx.App.Out, "Port: %d\n", port)
        }
        
        if timeout, ok := ctx.Int64("timeout"); ok {
                fmt.Fprintf(ctx.App.Out, "Timeout: %d\n", timeout)
        }
        
        if ratio, ok := ctx.Float64("ratio"); ok {
                fmt.Fprintf(ctx.App.Out, "Ratio: %.2f\n", ratio)
        }

        // Use context.Context functionality (cancellation, deadlines)
        select {
        case <-ctx.Done():
                return ctx.Err()
        default:
        }

        // Pass ctx directly to Prompter (it embeds context.Context)
        value, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
                Label: "Enter value",
        })
        if err != nil {
                return err
        }

        return nil
}

Extensions

Extensions provide optional "batteries-included" features that can be added to your CLI application without adding overhead for simple apps that don't need them.

This design is inspired by goldmark's extension system, which allows features to be added without polluting the core library.

Philosophy

Simple by default, powerful when needed. clix starts with minimal overhead:

  • Core command/flag/argument parsing
  • Flag-based help (-h, --help) - always available
  • Prompting UI
  • Configuration management (API only, not commands)

Everything else is opt-in via extensions, including:

  • Command-based help (cli help)
  • Config management commands (cli config)
  • Shell completion (cli autocomplete)
  • Version information (cli version)
  • And future extensions...
Using Extensions

Add extensions to your app before calling Run():

import (
        "clix"
        "clix/ext/autocomplete"
        "clix/ext/config"
        "clix/ext/help"
        "clix/ext/version"
)

app := clix.NewApp("myapp")
app.Root = clix.NewCommand("myapp")

// Add extensions for optional features
app.AddExtension(help.Extension{})         // Adds: myapp help [command]
app.AddExtension(config.Extension{})       // Adds: myapp config, myapp config list, etc.
app.AddExtension(autocomplete.Extension{}) // Adds: myapp autocomplete [shell]
app.AddExtension(version.Extension{        // Adds: myapp version
        Version: "1.0.0",
        Commit:  "abc123",  // optional
        Date:    "2024-01-01", // optional
})

// Flag-based help works without extensions: myapp -h, myapp --help
app.Run(context.Background(), nil)
Available Extensions
Help Extension (clix/ext/help)

Adds command-based help similar to man pages:

  • cli help - Show help for the root command
  • cli help [command] - Show help for a specific command

Note: Flag-based help (-h, --help) is handled by the core library and works without this extension. This extension only adds the help command itself.

Config Extension (clix/ext/config)

Adds configuration management commands using dot-separated key paths (e.g. project.default):

  • cli config - Show help for config commands (group)
  • cli config list - List persisted configuration as YAML (respects --format=json|yaml|text)
  • cli config get <key_path> - Print the persisted value at key_path
  • cli config set <key_path> <value> - Persist a new value
  • cli config unset <key_path> - Remove the persisted value (no-op if missing)
  • cli config reset - Remove all persisted configuration from disk (flags/env/defaults still apply)

Optional schemas can enforce types when setting values:

app.Config.RegisterSchema(clix.ConfigSchema{
        Key:  "project.retries",
        Type: clix.ConfigInteger,
})

With a schema, config set project.retries 5 succeeds while config set project.retries nope fails with a helpful error.

Autocomplete Extension (clix/ext/autocomplete)

Adds shell completion script generation:

  • cli autocomplete [bash|zsh|fish] - Generate completion script for the specified shell
  • If no shell is provided, shows help
Version Extension (clix/ext/version)

Adds version information:

  • cli version - Show version information, including Go version and build info (supports --format=json|yaml|text)
  • Global --version/-v flag - Show simple version string (e.g., cli --version shows "cli version 1.0.0")
app.AddExtension(version.Extension{
        Version: "1.0.0",
        Commit:  "abc123",  // optional
        Date:    "2024-01-01", // optional
})

Zero overhead if not imported: Extensions only add commands when imported and registered. Simple apps that don't import them pay zero cost.

Creating Extensions

Extensions implement the clix.Extension interface:

package myextension

import "github.com/SCKelemen/clix"

type Extension struct {
        // Optional: extension-specific configuration
}

func (e Extension) Extend(app *clix.App) error {
        // Add commands, modify behavior, etc.
        if app.Root != nil {
                app.Root.AddCommand(MyCustomCommand(app))
        }
        return nil
}

Extensions are applied lazily when Run() is called, or can be applied early with ApplyExtensions().

For more details, see ext/README.md.

Key API Types

clix.App

The App struct represents a runnable CLI application and wires together the root command, global flag set, configuration manager, and prompting behavior.

type App struct {
        Name        string
        Version     string
        Description string

        Root        *Command
        Config      *ConfigManager
        Prompter    Prompter
        Out         io.Writer
        Err         io.Writer
        In          io.Reader
        EnvPrefix   string

        DefaultTheme  PromptTheme
        Styles        Styles
}

Key methods:

  • NewApp(name string) *App - Construct a new application
  • Run(ctx context.Context, args []string) error - Execute the application
  • AddExtension(ext Extension) - Register an extension
  • ApplyExtensions() error - Apply all registered extensions
  • OutputFormat() string - Get the current output format (json/yaml/text)
  • FormatOutput(data interface{}) error - Format data using the current format
clix.Command

A Command represents a CLI command. Commands can contain nested children (groups or commands), flags, argument definitions, and execution hooks.

A Command can be one of three types:

  • Group: has children but no Run handler (interior node, shows help when called)
  • Leaf Command: has a Run handler but no children (executable leaf node)
  • Command with Children: has both a Run handler and children (executes Run handler when called without args, or routes to child commands when a child name is provided)
type Command struct {
        Name        string
        Aliases     []string
        Short       string
        Long        string
        Usage       string
        Example     string
        Hidden      bool
        Flags       *FlagSet
        Arguments   []*Argument
        Children    []*Command // Children of this command (groups or commands)

        Run     Handler
        PreRun  Hook
        PostRun Hook
}

Key methods:

  • NewCommand(name string) *Command - Construct a new executable command
  • NewGroup(name, short string, children ...*Command) *Command - Construct a group (interior node)
  • AddCommand(cmd *Command) - Register a child command or group
  • IsGroup() bool - Returns true if command is a group (has children, no Run handler)
  • IsLeaf() bool - Returns true if command is executable (has Run handler)
  • Groups() []*Command - Returns only child groups
  • Commands() []*Command - Returns only executable child commands
  • VisibleChildren() []*Command - Returns all visible child commands and groups
  • Path() string - Get the full command path from root
clix.Argument

An Argument describes a positional argument for a command.

type Argument struct {
        Name     string
        Prompt   string
        Default  string
        Required bool
        Validate func(string) error
}

Methods:

  • PromptLabel() string - Get the prompt label (defaults to title-cased name)
clix.PromptRequest

A PromptRequest carries the information necessary to display a prompt. This is the primary, struct-based API for prompts, consistent with the rest of the codebase (similar to StringVarOptions, BoolVarOptions, etc.). PromptRequest implements PromptOption, so it can be used alongside functional options.

type PromptRequest struct {
        Label    string
        Default  string
        Validate func(string) error
        Theme    PromptTheme

        // Options for select-style prompts
        Options []SelectOption

        // MultiSelect enables multi-selection mode
        MultiSelect bool

        // Confirm is for yes/no confirmation prompts
        Confirm bool

        // ContinueText for multi-select prompts
        ContinueText string
}

Functional Options API:

For convenience, clix also provides functional options that can be used instead of or alongside PromptRequest:

Core options (available in clix package):

  • WithLabel(label string) - Set the prompt label
  • WithDefault(def string) - Set the default value
  • WithValidate(validate func(string) error) - Set validation function
  • WithTheme(theme PromptTheme) - Set the prompt theme
  • WithConfirm() - Enable yes/no confirmation prompt
  • WithCommandHandler(handler PromptCommandHandler) - Register handler for special key commands
  • WithKeyMap(keyMap PromptKeyMap) - Configure keyboard shortcuts and bindings
  • WithNoDefaultPlaceholder(text string) - Set placeholder text when no default exists

Advanced options (require clix/ext/prompt extension):

  • prompt.Select(options []SelectOption) - Create a select prompt
  • prompt.MultiSelect(options []SelectOption) - Create a multi-select prompt
  • prompt.WithContinueText(text string) - Set continue button text for multi-select
  • prompt.Confirm() - Alias for WithConfirm() (for convenience)

Both APIs can be mixed. For example:

// Mix struct with functional options
result, err := prompter.Prompt(ctx,
        clix.PromptRequest{Label: "Name"},
        clix.WithDefault("unknown"),
)
clix.Extension

The Extension interface allows optional features to be added to an application.

type Extension interface {
        Extend(app *App) error
}

Examples

  • examples/basic: End-to-end application demonstrating commands, flags, prompting, and configuration usage.
  • examples/gh: A GitHub CLI-style hierarchy with familiar groups, commands, aliases, and interactive prompts.
  • examples/gcloud: A Google Cloud CLI-inspired tree with large command groups, global flags, and configuration interactions.
  • examples/lipgloss: Demonstrates prompt and help styling using lipgloss, including select, multi-select, and confirm prompts.
  • examples/multicli: Demonstrates sharing command implementations across multiple CLI applications with different hierarchies, similar to Google Cloud's gcloud/bq pattern.

Contributing

We welcome issues and pull requests. To keep the review cycle short:

  1. Fork the repo and create a feature branch.
  2. Format Go sources with gofmt (or go fmt ./...).
  3. Run go test ./... to ensure tests and examples stay green.
  4. Include tests and docs for new behavior.

If you plan a larger change, feel free to open an issue first so we can discuss the approach.

Release Process

We use semantic versioning and Git tags so Go tooling (and Dependabot) can pick up new versions automatically. When you’re ready to cut a release:

  1. Ensure main is green: gofmt ./... && go test ./....
  2. Update any docs or examples that mention the version (e.g., changelog snippets if applicable).
  3. Tag the release using Go-style semver and push the tag:
    git tag -a v1.0.0 -m "clix v1.0.0"
    git push origin v1.0.0
    
  4. GitHub Actions (release.yml) will build/test and publish a GitHub Release for that tag. pkg.go.dev and Dependabot will automatically index the new version.

Following this flow keeps the module path (github.com/SCKelemen/clix) stable and gives downstream consumers reproducible builds.

Developers & Maintainers

Receiving Dependabot PRs When clix Releases

Whenever we tag a new version (e.g. v1.0.0, v1.1.0, …) the Go module proxy and Dependabot can see it automatically because the module path stays github.com/SCKelemen/clix. To have Dependabot open upgrade PRs in your application:

  1. Import clix via the module path:
    import "github.com/SCKelemen/clix"
    
  2. Pin a version in your go.mod:
    require github.com/SCKelemen/clix v1.0.0
    
    Dependabot will bump this line when a newer semver-compatible version exists.
  3. Add .github/dependabot.yml with a Go entry:
    version: 2
    updates:
      - package-ecosystem: "gomod"
        directory: "/"          # path containing go.mod
        schedule:
          interval: "weekly"    # or daily/monthly
    

That’s it—each time clix tags a new release, Dependabot compares the version in your go.mod with the latest tag and submits a PR if they differ. If you want to limit updates (e.g. only patches), you can use Dependabot’s allow/ignore rules, but the basic config above is enough for automatic PRs.

License

clix is available under the MIT License.

Documentation

Overview

Package clix provides a declarative framework for building command line interfaces with nested commands, global flags, configuration hydration and interactive prompting. The package exposes high level types such as App and Command which can be composed to describe the command tree and execution behaviour of a CLI.

Getting Started

Define an App, create commands with handlers, and call Run:

app := clix.NewApp("myapp")
app.Root = clix.NewGroup("myapp", "My application",
	clix.NewCommand("greet", "Greet someone", func(ctx *clix.Context) error {
		name, _ := ctx.Arg(0)
		fmt.Printf("Hello, %s!\n", name)
		return nil
	}),
)
app.Run(context.Background(), nil)

Core Types

The primary stable types for v1 are:

  • App – owns the root command, flags, and extensions
  • Command – represents a node in the CLI tree (group or command)
  • Context – wraps context.Context with CLI metadata (App, Command, Args)
  • Extension – plugs cross-cutting behavior into App

Context Usage

clix uses a layered context design:

  • App.Run(ctx context.Context, args []string) accepts a standard context.Context that controls process-level cancellation and deadlines.

  • For each command execution, clix builds a *clix.Context which embeds the original context.Context and adds CLI-specific data (App, Command, Args, hydrated flags/config).

  • Within command handlers, pass *clix.Context to any functions that need CLI metadata. Because *clix.Context embeds context.Context, you can pass it anywhere a context.Context is required (e.g., to Prompter.Prompt).

  • Internal clix functions that need CLI awareness (App, Command, Args) should accept *clix.Context. Functions that only need cancellation/deadlines should accept context.Context.

Example:

cmd.Run = func(ctx *clix.Context) error {
	// ctx is both a context.Context (for cancellation) and has CLI data
	value, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
		Label: "Enter value",
	})
	// Use ctx.Done(), ctx.Err() for cancellation
	// Use ctx.App, ctx.Command, ctx.Args for CLI data
	return nil
}

Compatibility

clix v1 follows semantic versioning. The github.com/SCKelemen/clix module path will not have breaking changes within major version 1. Extensions under github.com/SCKelemen/clix/ext/... may evolve more quickly but will also respect semver for their exported APIs.

clix requires Go 1.24 or later.

Example (DeclarativeStyle)

Example_declarativeStyle demonstrates a purely declarative CLI app using only struct-based APIs throughout.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	var (
		project string
		verbose bool
		port    int
	)

	// Create app using struct fields
	app := clix.NewApp("myapp")
	app.Description = "A declarative-style CLI application"
	app.Version = "1.0.0"

	// Global flags using struct-based API
	app.Flags().StringVar(clix.StringVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:   "project",
			Short:  "p",
			Usage:  "Project to operate on",
			EnvVar: "MYAPP_PROJECT",
		},
		Default: "default-project",
		Value:   &project,
	})

	app.Flags().BoolVar(clix.BoolVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:  "verbose",
			Short: "v",
			Usage: "Enable verbose output",
		},
		Value: &verbose,
	})

	// Commands using struct fields
	createCmd := clix.NewCommand("create")
	createCmd.Short = "Create a new resource"
	createCmd.Run = func(ctx *clix.Context) error {
		name, _ := ctx.String("project")
		fmt.Fprintf(ctx.App.Out, "Creating resource in project: %s\n", name)
		return nil
	}

	// Command flags using struct-based API
	createCmd.Flags.IntVar(clix.IntVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:  "port",
			Usage: "Server port",
		},
		Default: "8080",
		Value:   &port,
	})

	// Arguments using struct-based API
	createCmd.Arguments = []*clix.Argument{
		{
			Name:     "name",
			Prompt:   "Enter resource name",
			Required: true,
		},
		{
			Name:    "email",
			Prompt:  "Enter email address",
			Default: "[email protected]",
		},
	}

	// Config schema using struct-based API
	app.Config.RegisterSchema(clix.ConfigSchema{
		Key:  "project.retries",
		Type: clix.ConfigInteger,
	})

	// Build command tree
	listCmd := clix.NewCommand("list")
	listCmd.Short = "List resources"
	listCmd.Run = func(ctx *clix.Context) error {
		fmt.Fprintln(ctx.App.Out, "Listing resources...")
		return nil
	}

	root := clix.NewGroup("myapp", "My application", createCmd, listCmd)
	app.Root = root

	// Run the app
	_ = app
}
Example (FluentStyle)

Example_fluentStyle demonstrates a fully fluent-style CLI app using only builder-style (method chaining) APIs throughout.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	var (
		project string
		verbose bool
		port    int
	)

	// Create app using builder-style
	app := clix.NewApp("myapp").
		SetDescription("A fluent-style CLI application").
		SetVersion("1.0.0")

	// Global flags using builder-style
	projectFlagOpts := &clix.StringVarOptions{}
	projectFlagOpts.SetName("project").
		SetShort("p").
		SetUsage("Project to operate on").
		SetEnvVar("MYAPP_PROJECT").
		SetDefault("default-project").
		SetValue(&project)
	app.Flags().StringVar(*projectFlagOpts)

	verboseFlagOpts := &clix.BoolVarOptions{}
	verboseFlagOpts.SetName("verbose").
		SetShort("v").
		SetUsage("Enable verbose output").
		SetValue(&verbose)
	app.Flags().BoolVar(*verboseFlagOpts)

	// Commands using builder-style
	createCmd := clix.NewCommand("create").
		SetShort("Create a new resource").
		SetRun(func(ctx *clix.Context) error {
			name, _ := ctx.String("project")
			fmt.Fprintf(ctx.App.Out, "Creating resource in project: %s\n", name)
			return nil
		})

	// Command flags using builder-style
	portFlagOpts := &clix.IntVarOptions{}
	portFlagOpts.SetName("port").
		SetUsage("Server port").
		SetDefault("8080").
		SetValue(&port)
	createCmd.Flags.IntVar(*portFlagOpts)

	// Arguments using builder-style
	createCmd.Arguments = []*clix.Argument{
		clix.NewArgument().
			SetName("name").
			SetPrompt("Enter resource name").
			SetRequired(),
		clix.NewArgument().
			SetName("email").
			SetPrompt("Enter email address").
			SetDefault("[email protected]"),
	}

	// Config schema using builder-style
	schema := &clix.ConfigSchema{}
	schema.SetKey("project.retries").
		SetType(clix.ConfigInteger)
	app.Config.RegisterSchema(*schema)

	// Build command tree using builder-style
	listCmd := clix.NewCommand("list").
		SetShort("List resources").
		SetRun(func(ctx *clix.Context) error {
			fmt.Fprintln(ctx.App.Out, "Listing resources...")
			return nil
		})

	root := clix.NewGroup("myapp", "My application", createCmd, listCmd)
	app.SetRoot(root)

	// Run the app
	_ = app
}
Example (FunctionalStyle)

Example_functionalStyle demonstrates a purely functional-style CLI app using only functional options APIs throughout.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	var (
		project string
		verbose bool
		port    int
	)

	// Create app with functional options
	app := clix.NewApp("myapp")
	app.Description = "A fully functional-style CLI application"

	// Global flags using functional options
	app.Flags().StringVar(
		clix.WithFlagName("project"),
		clix.WithFlagShort("p"),
		clix.WithFlagUsage("Project to operate on"),
		clix.WithFlagEnvVar("MYAPP_PROJECT"),
		clix.WithStringValue(&project),
		clix.WithStringDefault("default-project"),
	)

	app.Flags().BoolVar(
		clix.WithFlagName("verbose"),
		clix.WithFlagShort("v"),
		clix.WithFlagUsage("Enable verbose output"),
		clix.WithBoolValue(&verbose),
	)

	// Commands using functional options
	createCmd := clix.NewCommand("create",
		clix.WithCommandShort("Create a new resource"),
		clix.WithCommandRun(func(ctx *clix.Context) error {
			name, _ := ctx.String("project")
			fmt.Fprintf(ctx.App.Out, "Creating resource in project: %s\n", name)
			return nil
		}),
	)

	// Command flags using functional options
	createCmd.Flags.IntVar(
		clix.WithFlagName("port"),
		clix.WithFlagUsage("Server port"),
		clix.WithIntegerValue(&port),
		clix.WithIntegerDefault("8080"),
	)

	// Arguments using functional options
	createCmd.Arguments = []*clix.Argument{
		clix.NewArgument(
			clix.WithArgName("name"),
			clix.WithArgPrompt("Enter resource name"),
			clix.WithArgRequired(),
		),
		clix.NewArgument(
			clix.WithArgName("email"),
			clix.WithArgPrompt("Enter email address"),
			clix.WithArgDefault("[email protected]"),
		),
	}

	// Config schema using functional options
	app.Config.RegisterSchema(
		clix.WithConfigKey("project.retries"),
		clix.WithConfigType(clix.ConfigInteger),
	)

	// Build command tree
	root := clix.NewGroup("myapp", "My application",
		createCmd,
		clix.NewCommand("list",
			clix.WithCommandShort("List resources"),
			clix.WithCommandRun(func(ctx *clix.Context) error {
				fmt.Fprintln(ctx.App.Out, "Listing resources...")
				return nil
			}),
		),
	)

	app.Root = root

	// Run the app
	_ = app
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultPromptTheme = PromptTheme{
	Prefix: "? ",
	Hint:   "",
	Error:  "! ",
}

DefaultPromptTheme provides a sensible default for terminal prompts.

View Source
var DefaultStyles = Styles{}

DefaultStyles leaves all styles unset, producing plain text output.

Functions

func FormatData

func FormatData(w io.Writer, data interface{}, format string) error

FormatData formats data to the specified output writer using the given format.

Types

type App

type App struct {
	// Name is the application name, used in help output and configuration paths.
	Name string

	// Version is the application version, typically set by the version extension.
	// Can be accessed via --version flag when the version extension is enabled.
	Version string

	// Description is a brief description of the application, shown in help output.
	Description string

	// Root is the root command of the application. Flags defined on the root
	// command are accessible to all commands (via app.Flags()).
	// NewApp automatically creates a minimal root command, but you can replace it.
	Root *Command

	// Config manages application configuration from YAML files and environment variables.
	// Configuration values are automatically loaded and available via Context getters.
	Config *ConfigManager

	// Prompter handles interactive user input. Defaults to TextPrompter.
	// Use the prompt extension to enable advanced prompts (select, multi-select).
	Prompter Prompter

	// Out is the writer for standard output (defaults to os.Stdout).
	Out io.Writer

	// Err is the writer for error output (defaults to os.Stderr).
	Err io.Writer

	// In is the reader for user input (defaults to os.Stdin).
	In io.Reader

	// EnvPrefix is the prefix for environment variable names.
	// Defaults to the app name in uppercase with hyphens replaced by underscores.
	// For example, "my-app" becomes "MY_APP".
	EnvPrefix string

	// DefaultTheme configures the default styling for prompts.
	// Can be overridden per-prompt via PromptRequest.Theme.
	DefaultTheme PromptTheme

	// Styles configures text styling for help output and other CLI text.
	// Use lipgloss-compatible styles or custom TextStyle implementations.
	Styles Styles
	// contains filtered or unexported fields
}

App represents a runnable CLI application. It wires together the root command, global flag set, configuration manager and prompting behaviour.

Example:

app := clix.NewApp("myapp")
app.Description = "A sample CLI application"
app.Root = clix.NewGroup("myapp", "Main command",
	clix.NewCommand("hello", "Say hello", func(ctx *clix.Context) error {
		fmt.Println("Hello, World!")
		return nil
	}),
)
app.Run(context.Background(), nil)

func NewApp

func NewApp(name string, opts ...AppOption) *App

NewApp constructs an application with sensible defaults. A minimal root command is created automatically to hold default flags (format, help). You can replace it with your own root command if needed: app.Root = clix.NewCommand("myroot")

Example - three API styles:

// 1. Struct-based (primary API)
app := clix.NewApp("myapp")
app.Description = "My application"
app.Version = "1.0.0"

// 2. Functional options
app := clix.NewApp("myapp",
	clix.WithAppDescription("My application"),
	clix.WithAppVersion("1.0.0"),
)

// 3. Builder-style
app := clix.NewApp("myapp").
	SetDescription("My application").
	SetVersion("1.0.0")

Note: While you can set Version directly, use the version extension to get `cli --version` and `cli version` commands. The extension will set this field.

Example

ExampleNewApp demonstrates how to create a new CLI application.

package main

import (
	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")
	app.Description = "A sample CLI application"

	// Create a root command
	root := clix.NewCommand("myapp")
	root.Short = "Root command"
	app.Root = root

	// The app is now ready to use
	_ = app
}

func (*App) AddDefaultCommands

func (a *App) AddDefaultCommands()

AddDefaultCommands attaches built-in helper commands to the application.

Note: All commands are now extensions: - Help: clix/ext/help - Config: clix/ext/config - Autocomplete: clix/ext/autocomplete - Version: clix/ext/version No default commands are added automatically.

func (*App) AddExtension

func (a *App) AddExtension(ext Extension)

AddExtension registers an extension with the application. Extensions are applied lazily when the app runs, or can be applied immediately by calling ApplyExtensions().

func (*App) ApplyExtensions

func (a *App) ApplyExtensions() error

ApplyExtensions processes all registered extensions in order. This is typically called automatically during Run(), but can be called manually for testing or early initialization.

func (*App) ConfigDir

func (a *App) ConfigDir() (string, error)

ConfigDir returns the absolute path to the application's configuration directory. The directory will be created if it does not already exist.

Example

ExampleApp_ConfigDir demonstrates how to work with configuration files.

package main

import (
	"fmt"
	"os"

	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")

	// Get the config directory (creates if it doesn't exist)
	configDir, err := app.ConfigDir()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		return
	}
	fmt.Printf("Config directory: %s\n", configDir)

	// Get the config file path
	configFile, err := app.ConfigFile()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		return
	}
	fmt.Printf("Config file: %s\n", configFile)

	// Set a config value
	app.Config.Set("project", "my-project")

	// Save to disk
	if err := app.SaveConfig(); err != nil {
		fmt.Fprintf(os.Stderr, "Error saving config: %v\n", err)
	}
}

func (*App) ConfigFile

func (a *App) ConfigFile() (string, error)

ConfigFile returns the path to the main configuration file.

func (*App) Flags

func (a *App) Flags() *FlagSet

Flags returns the flag set for the root command. Flags defined on the root command apply to all commands (they are "global" by virtue of being on the root). This provides a symmetric API with cmd.Flags.

func (*App) FormatOutput

func (a *App) FormatOutput(data interface{}) error

FormatOutput formats data according to the app's output format setting. It supports "json", "yaml", and "text" formats. For maps/slices, it automatically formats them appropriately. For other types, it falls back to text formatting.

Example

ExampleApp_FormatOutput demonstrates how to use structured output formatting.

package main

import (
	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")
	root := clix.NewCommand("myapp")
	app.Root = root

	root.Run = func(ctx *clix.Context) error {
		data := map[string]interface{}{
			"name":   "John Doe",
			"age":    30,
			"active": true,
			"tags":   []string{"developer", "golang"},
		}

		// FormatOutput respects the --format flag (json, yaml, or text)
		// Users can run: myapp --format=json
		return ctx.App.FormatOutput(data)
	}
}

func (*App) OutputFormat

func (a *App) OutputFormat() string

OutputFormat returns the currently selected output format. Valid values are "json", "yaml", or "text" (default).

func (*App) Run

func (a *App) Run(ctx context.Context, args []string) error

Run executes the CLI using the provided arguments. If args is nil the process arguments (os.Args[1:]) are used.

Example

ExampleApp_Run demonstrates a complete application setup and execution.

package main

import (
	"context"
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("greet")
	app.Description = "A greeting application"

	var name string
	app.Flags().StringVar(clix.StringVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:  "name",
			Short: "n",
			Usage: "Name to greet",
		},
		Value:   &name,
		Default: "World",
	})

	// Use the root command created by NewApp, just customize it
	app.Root.Short = "Print a greeting"
	app.Root.Run = func(ctx *clix.Context) error {
		fmt.Fprintf(ctx.App.Out, "Hello, %s!\n", name)
		return nil
	}

	// In a real application, you would call:
	// if err := app.Run(context.Background(), nil); err != nil {
	//     fmt.Fprintln(app.Err, err)
	//     os.Exit(1)
	// }

	// For this example, we'll simulate running with arguments
	ctx := context.Background()
	args := []string{"--name", "Alice"}
	_ = app.Run(ctx, args)
}
Output:

Hello, Alice!

func (*App) SaveConfig

func (a *App) SaveConfig() error

SaveConfig persists the configuration manager's values to disk.

func (*App) SetDefaultTheme

func (a *App) SetDefaultTheme(theme PromptTheme) *App

SetDefaultTheme sets the default prompt theme and returns the app for method chaining.

func (*App) SetDescription

func (a *App) SetDescription(description string) *App

SetDescription sets the application description and returns the app for method chaining.

func (*App) SetEnvPrefix

func (a *App) SetEnvPrefix(prefix string) *App

SetEnvPrefix sets the environment variable prefix and returns the app for method chaining.

func (*App) SetErr

func (a *App) SetErr(err io.Writer) *App

SetErr sets the error writer and returns the app for method chaining.

func (*App) SetIn

func (a *App) SetIn(in io.Reader) *App

SetIn sets the input reader and returns the app for method chaining.

func (*App) SetOut

func (a *App) SetOut(out io.Writer) *App

SetOut sets the output writer and returns the app for method chaining.

func (*App) SetPrompter

func (a *App) SetPrompter(prompter Prompter) *App

SetPrompter sets the prompter and returns the app for method chaining.

func (*App) SetRoot

func (a *App) SetRoot(root *Command) *App

SetRoot sets the root command and returns the app for method chaining.

func (*App) SetStyles

func (a *App) SetStyles(styles Styles) *App

SetStyles sets the application styles and returns the app for method chaining.

func (*App) SetVersion

func (a *App) SetVersion(version string) *App

SetVersion sets the application version and returns the app for method chaining. Note: While you can set Version directly, use the version extension to get `cli --version` and `cli version` commands. The extension will set this field.

type AppOption

type AppOption interface {
	// ApplyApp configures an App struct.
	// Exported so extension packages can implement AppOption.
	ApplyApp(*App)
}

AppOption configures an App using the functional options pattern. Options can be used to build apps:

// Using functional options
app := clix.NewApp("myapp",
	clix.WithAppDescription("My application"),
	clix.WithAppVersion("1.0.0"),
	clix.WithAppEnvPrefix("MYAPP"),
)

// Using struct (primary API)
app := clix.NewApp("myapp")
app.Description = "My application"

func WithAppDefaultTheme

func WithAppDefaultTheme(theme PromptTheme) AppOption

WithAppDefaultTheme sets the default prompt theme.

func WithAppDescription

func WithAppDescription(description string) AppOption

WithAppDescription sets the application description.

func WithAppEnvPrefix

func WithAppEnvPrefix(prefix string) AppOption

WithAppEnvPrefix sets the environment variable prefix.

func WithAppErr

func WithAppErr(err io.Writer) AppOption

WithAppErr sets the error writer.

func WithAppIn

func WithAppIn(in io.Reader) AppOption

WithAppIn sets the input reader.

func WithAppOut

func WithAppOut(out io.Writer) AppOption

WithAppOut sets the output writer.

func WithAppPrompter

func WithAppPrompter(prompter Prompter) AppOption

WithAppPrompter sets the prompter.

func WithAppRoot

func WithAppRoot(root *Command) AppOption

WithAppRoot sets the root command.

func WithAppStyles

func WithAppStyles(styles Styles) AppOption

WithAppStyles sets the application styles.

func WithAppVersion

func WithAppVersion(version string) AppOption

WithAppVersion sets the application version. Note: While you can set Version directly, use the version extension to get `cli --version` and `cli version` commands. The extension will set this field.

type Argument

type Argument struct {
	// Name is the argument name, used for named argument access (key=value format)
	// and for accessing via ctx.ArgNamed(name).
	Name string

	// Prompt is the text shown when prompting for this argument if it's missing.
	// If empty, a default prompt is generated from the Name field.
	Prompt string

	// Default is the default value if the argument is not provided.
	// Only used if Required is false.
	Default string

	// Required indicates whether this argument must be provided.
	// Missing required arguments trigger interactive prompts (if available).
	Required bool

	// Validate is an optional validation function called when the argument is provided.
	// Return an error if the value is invalid.
	Validate func(string) error
}

Argument describes a positional argument for a command. This struct implements ArgumentOption, so it can be used alongside functional options.

Example:

// Struct-based (primary API)
cmd.Arguments = []*clix.Argument{
	{
		Name:     "name",
		Prompt:   "Enter your name",
		Required: true,
		Validate: validation.NotEmpty,
	},
	{
		Name:    "email",
		Prompt:  "Enter your email",
		Default: "[email protected]",
		Validate: validation.Email,
	},
}

// Functional options
cmd.Arguments = []*clix.Argument{
	clix.NewArgument(
		WithArgName("name"),
		WithArgPrompt("Enter your name"),
		WithArgRequired(),
		WithArgValidate(validation.NotEmpty),
	),
	clix.NewArgument(
		WithArgName("email"),
		WithArgPrompt("Enter your email"),
		WithArgDefault("[email protected]"),
		WithArgValidate(validation.Email),
	),
}
Example

ExampleArgument demonstrates how to define positional arguments with prompting.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	cmd := clix.NewCommand("greet")
	cmd.Short = "Print a greeting"

	// Required argument with automatic prompting
	cmd.Arguments = []*clix.Argument{
		{
			Name:     "name",
			Prompt:   "What is your name?",
			Required: true,
			Validate: func(value string) error {
				if len(value) < 2 {
					return fmt.Errorf("name must be at least 2 characters")
				}
				return nil
			},
		},
		{
			Name:    "title",
			Prompt:  "What is your title?",
			Default: "Developer",
		},
	}

	cmd.Run = func(ctx *clix.Context) error {
		name := ctx.Args[0]
		title := ctx.Args[1]
		fmt.Fprintf(ctx.App.Out, "Hello %s, %s!\n", title, name)
		return nil
	}
}

func NewArgument

func NewArgument(opts ...ArgumentOption) *Argument

NewArgument creates a new Argument using functional options. Supports three API styles:

// 1. Struct-based (primary API)
arg := &clix.Argument{
	Name:     "name",
	Prompt:   "Enter your name",
	Required: true,
}

// 2. Functional options
arg := clix.NewArgument(
	WithArgName("name"),
	WithArgPrompt("Enter your name"),
	WithArgRequired(),
)

// 3. Builder-style (fluent API)
arg := clix.NewArgument().
	SetName("name").
	SetPrompt("Enter your name").
	SetRequired()

func (*Argument) ApplyArgument

func (a *Argument) ApplyArgument(arg *Argument)

ApplyArgument implements ArgumentOption so Argument can be used directly.

func (*Argument) PromptLabel

func (a *Argument) PromptLabel() string

PromptLabel returns the prompt to display for this argument.

func (*Argument) SetDefault

func (a *Argument) SetDefault(defaultValue string) *Argument

SetDefault sets the argument default value and returns the argument for method chaining.

func (*Argument) SetName

func (a *Argument) SetName(name string) *Argument

SetName sets the argument name and returns the argument for method chaining.

func (*Argument) SetPrompt

func (a *Argument) SetPrompt(prompt string) *Argument

SetPrompt sets the argument prompt text and returns the argument for method chaining.

func (*Argument) SetRequired

func (a *Argument) SetRequired() *Argument

SetRequired marks the argument as required and returns the argument for method chaining.

func (*Argument) SetValidate

func (a *Argument) SetValidate(validate func(string) error) *Argument

SetValidate sets the argument validation function and returns the argument for method chaining.

type ArgumentOption

type ArgumentOption interface {
	// ApplyArgument configures an argument struct.
	// Exported so extension packages can implement ArgumentOption.
	ApplyArgument(*Argument)
}

ArgumentOption configures an argument using the functional options pattern. Options can be used to build arguments:

// Using functional options
cmd.Arguments = []*clix.Argument{
	clix.NewArgument(
		WithArgName("name"),
		WithArgPrompt("Enter your name"),
		WithArgRequired(),
		WithArgValidate(validation.NotEmpty),
	),
}

// Using struct (primary API)
cmd.Arguments = []*clix.Argument{{
	Name:     "name",
	Prompt:   "Enter your name",
	Required: true,
	Validate: validation.NotEmpty,
}}

func WithArgDefault

func WithArgDefault(defaultValue string) ArgumentOption

WithArgDefault sets the argument default value.

func WithArgName

func WithArgName(name string) ArgumentOption

WithArgName sets the argument name.

func WithArgPrompt

func WithArgPrompt(prompt string) ArgumentOption

WithArgPrompt sets the argument prompt text.

func WithArgRequired

func WithArgRequired() ArgumentOption

WithArgRequired marks the argument as required.

func WithArgValidate

func WithArgValidate(validate func(string) error) ArgumentOption

WithArgValidate sets the argument validation function.

type BoolValue

type BoolValue struct {
	// contains filtered or unexported fields
}

BoolValue implements Value for boolean flags.

func (*BoolValue) Bool

func (b *BoolValue) Bool() bool

func (*BoolValue) Set

func (b *BoolValue) Set(value string) error

func (*BoolValue) SetBool

func (b *BoolValue) SetBool(value bool) error

func (*BoolValue) String

func (b *BoolValue) String() string

type BoolVarOptions

type BoolVarOptions struct {
	FlagOptions
	// Value is a pointer to the variable that will store the flag value.
	Value *bool
}

BoolVarOptions describes the configuration for adding a bool flag. This struct implements FlagOption, so it can be used alongside functional options.

Example:

var verbose bool
// Struct-based (primary API)
cmd.Flags.BoolVar(clix.BoolVarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "verbose",
		Short: "v",
		Usage: "Enable verbose output",
	},
	Value: &verbose,
})

// Functional options
cmd.Flags.BoolVar(
	WithFlagName("verbose"),
	WithFlagShort("v"),
	WithFlagUsage("Enable verbose output"),
	WithBoolValue(&verbose),
)

func (BoolVarOptions) ApplyFlag

func (o BoolVarOptions) ApplyFlag(fo *FlagOptions)

ApplyFlag implements FlagOption so BoolVarOptions can be used directly.

func (*BoolVarOptions) SetEnvVar

func (o *BoolVarOptions) SetEnvVar(envVar string) *BoolVarOptions

SetEnvVar sets the environment variable name and returns the options for method chaining.

func (*BoolVarOptions) SetName

func (o *BoolVarOptions) SetName(name string) *BoolVarOptions

SetName sets the flag name and returns the options for method chaining.

func (*BoolVarOptions) SetShort

func (o *BoolVarOptions) SetShort(short string) *BoolVarOptions

SetShort sets the flag shorthand and returns the options for method chaining.

func (*BoolVarOptions) SetUsage

func (o *BoolVarOptions) SetUsage(usage string) *BoolVarOptions

SetUsage sets the flag usage text and returns the options for method chaining.

func (*BoolVarOptions) SetValue

func (o *BoolVarOptions) SetValue(value *bool) *BoolVarOptions

SetValue sets the value pointer and returns the options for method chaining.

type Command

type Command struct {
	// Name is the command name, used for matching and in help output.
	Name string

	// Aliases are alternative names for the command (e.g., ["ls", "list"]).
	Aliases []string

	// Short is a brief one-line description shown in command lists.
	Short string

	// Long is a detailed multi-line description shown in command help.
	Long string

	// Usage is a usage string shown in help output (e.g., "myapp cmd [flags] [args]").
	// If empty, a default usage string is generated.
	Usage string

	// Example shows example usage in help output.
	Example string

	// Hidden hides the command from help output and autocomplete.
	Hidden bool

	// Flags is the flag set for this command. Flags defined here are scoped to this command.
	// Use app.Flags() for flags that apply to all commands.
	Flags *FlagSet

	// Arguments defines the positional arguments this command accepts.
	// Arguments can be required or optional, and can prompt for missing values.
	Arguments []*Argument

	// Children are the child commands or groups of this command.
	// Use NewGroup() to create groups, NewCommand() to create executable commands.
	Children []*Command

	// Run is the handler executed when this command is invoked.
	// If the command has children and no Run handler, it shows help (group behavior).
	// If the command has both children and a Run handler, the Run handler executes
	// when called without matching child commands.
	Run Handler

	// PreRun is executed before Run. Useful for setup or validation.
	PreRun Hook

	// PostRun is executed after Run. Useful for cleanup or finalization.
	PostRun Hook
	// contains filtered or unexported fields
}

Command represents a CLI command. Commands can contain nested children (groups or commands), flags, argument definitions and execution hooks.

A Command can be one of three types:

  • A Group: has children but no Run handler (interior node, shows help when called)
  • A Leaf Command: has a Run handler but no children (executable leaf node)
  • A Command with Children: has both a Run handler and children (executes Run handler when called without args, or routes to child commands when a child name is provided)

Example:

// Create a group (organizes child commands)
projectGroup := clix.NewGroup("project", "Manage projects",
	clix.NewCommand("list", "List projects", listProjects),
	clix.NewCommand("get", "Get a project", getProject),
)

// Create a command (executable)
helloCmd := clix.NewCommand("hello")
helloCmd.Short = "Say hello"
helloCmd.Run = func(ctx *clix.Context) error {
	fmt.Println("Hello!")
	return nil
}

func NewCommand

func NewCommand(name string, opts ...CommandOption) *Command

NewCommand constructs a Command with an initialised flag set. This creates an executable command (leaf node) that can have a Run handler. Accepts optional CommandOption arguments for configuration.

Example - three API styles:

// 1. Struct-based (primary API)
cmd := clix.NewCommand("hello")
cmd.Short = "Say hello"
cmd.Run = func(ctx *clix.Context) error {
	fmt.Println("Hello!")
	return nil
}

// 2. Functional options
cmd := clix.NewCommand("hello",
	clix.WithCommandShort("Say hello"),
	clix.WithCommandRun(func(ctx *clix.Context) error {
		fmt.Println("Hello!")
		return nil
	}),
)

// 3. Mixed (constructor + struct fields)
cmd := clix.NewCommand("hello",
	clix.WithCommandShort("Say hello"),
)
cmd.Run = func(ctx *clix.Context) error { ... }
Example

ExampleNewCommand demonstrates how to create commands.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	// Create an executable command (leaf node)
	create := clix.NewCommand("create")
	create.Short = "Create a new user"
	create.Run = func(ctx *clix.Context) error {
		fmt.Fprintf(ctx.App.Out, "Creating user...\n")
		return nil
	}

	// Commands can have aliases
	list := clix.NewCommand("list")
	list.Aliases = []string{"ls", "l"}
	list.Short = "List all users"
}

func NewGroup

func NewGroup(name, short string, children ...*Command) *Command

NewGroup constructs a Command that acts as a group (interior node). Groups organize child commands but do not execute (no Run handler). Groups are used to create hierarchical command structures. Accepts optional CommandOption arguments for additional configuration.

// Struct-based (primary API)
group := clix.NewGroup("project", "Manage projects",
	clix.NewCommand("list", ...),
	clix.NewCommand("get", ...),
)
group.Long = "Detailed description..."

// Functional options
group := clix.NewGroup("project", "Manage projects",
	clix.NewCommand("list", ...),
	clix.NewCommand("get", ...),
	WithCommandLong("Detailed description..."),
)
Example

ExampleNewGroup demonstrates how to create groups and organize commands.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	// Create executable commands (leaf nodes)
	createUser := clix.NewCommand("create")
	createUser.Short = "Create a new user"
	createUser.Run = func(ctx *clix.Context) error {
		fmt.Fprintf(ctx.App.Out, "Creating user...\n")
		return nil
	}

	listUsers := clix.NewCommand("list")
	listUsers.Short = "List all users"
	listUsers.Run = func(ctx *clix.Context) error {
		fmt.Fprintf(ctx.App.Out, "Listing users...\n")
		return nil
	}

	// Create a group (interior node) that organizes child commands
	users := clix.NewGroup("users", "Manage user accounts",
		createUser,
		listUsers,
	)

	// Groups can contain nested groups
	listProjects := clix.NewCommand("list")
	listProjects.Short = "List projects"
	projects := clix.NewGroup("projects", "Manage projects", listProjects)

	// Build a command tree with groups and commands
	version := clix.NewCommand("version")
	version.Short = "Show version"

	root := clix.NewGroup("demo", "Demo application",
		users,
		projects,
		version,
	)

	_ = root
}

func (*Command) AddCommand

func (c *Command) AddCommand(cmd *Command)

AddCommand registers a child command or group. The parent/child relationship is managed automatically.

func (*Command) Commands

func (c *Command) Commands() []*Command

Commands returns the child commands that are executable (leaf nodes).

func (*Command) Groups

func (c *Command) Groups() []*Command

Groups returns the child commands that are groups (interior nodes).

func (*Command) IsGroup

func (c *Command) IsGroup() bool

IsGroup returns true if this command is a group (has children but no Run handler). Groups are interior nodes that organize child commands.

func (*Command) IsLeaf

func (c *Command) IsLeaf() bool

IsLeaf returns true if this command is a leaf (has a Run handler). Leaf commands are executable commands, even if they have flags or arguments.

func (*Command) Path

func (c *Command) Path() string

Path returns the command path from the root.

func (*Command) RequiredArgs

func (c *Command) RequiredArgs() int

RequiredArgs returns the number of required positional arguments.

func (*Command) ResolvePath

func (c *Command) ResolvePath(path []string) *Command

ResolvePath resolves a path like ["config", "set"] into a command.

func (*Command) SetAliases

func (c *Command) SetAliases(aliases ...string) *Command

SetAliases sets the command aliases and returns the command for method chaining.

func (*Command) SetArguments

func (c *Command) SetArguments(args ...*Argument) *Command

SetArguments sets the command arguments and returns the command for method chaining.

func (*Command) SetExample

func (c *Command) SetExample(example string) *Command

SetExample sets the command example string and returns the command for method chaining.

func (*Command) SetHidden

func (c *Command) SetHidden(hidden bool) *Command

SetHidden marks the command as hidden and returns the command for method chaining.

func (*Command) SetLong

func (c *Command) SetLong(long string) *Command

SetLong sets the command long description and returns the command for method chaining.

func (*Command) SetPostRun

func (c *Command) SetPostRun(postRun Hook) *Command

SetPostRun sets the command post-run hook and returns the command for method chaining.

func (*Command) SetPreRun

func (c *Command) SetPreRun(preRun Hook) *Command

SetPreRun sets the command pre-run hook and returns the command for method chaining.

func (*Command) SetRun

func (c *Command) SetRun(run Handler) *Command

SetRun sets the command run handler and returns the command for method chaining.

func (*Command) SetShort

func (c *Command) SetShort(short string) *Command

SetShort sets the command short description and returns the command for method chaining.

func (*Command) SetUsage

func (c *Command) SetUsage(usage string) *Command

SetUsage sets the command usage string and returns the command for method chaining.

func (*Command) VisibleChildren

func (c *Command) VisibleChildren() []*Command

VisibleChildren returns a sorted slice of child commands and groups that are not hidden.

type CommandOption

type CommandOption interface {
	// ApplyCommand configures a command struct.
	// Exported so extension packages can implement CommandOption.
	ApplyCommand(*Command)
}

CommandOption configures a command using the functional options pattern. Options can be used to build commands:

// Using functional options
cmd := clix.NewCommand("hello",
	WithCommandShort("Say hello"),
	WithCommandRun(func(ctx *clix.Context) error {
		fmt.Println("Hello!")
		return nil
	}),
)

// Using struct (primary API)
cmd := clix.NewCommand("hello")
cmd.Short = "Say hello"
cmd.Run = func(ctx *clix.Context) error { ... }

func WithCommandAliases

func WithCommandAliases(aliases ...string) CommandOption

WithCommandAliases sets the command aliases.

func WithCommandArguments

func WithCommandArguments(args ...*Argument) CommandOption

WithCommandArguments sets the command arguments.

func WithCommandExample

func WithCommandExample(example string) CommandOption

WithCommandExample sets the command example string.

func WithCommandHidden

func WithCommandHidden(hidden bool) CommandOption

WithCommandHidden marks the command as hidden.

func WithCommandLong

func WithCommandLong(long string) CommandOption

WithCommandLong sets the command long description.

func WithCommandPostRun

func WithCommandPostRun(postRun Hook) CommandOption

WithCommandPostRun sets the command post-run hook.

func WithCommandPreRun

func WithCommandPreRun(preRun Hook) CommandOption

WithCommandPreRun sets the command pre-run hook.

func WithCommandRun

func WithCommandRun(run Handler) CommandOption

WithCommandRun sets the command run handler.

func WithCommandShort

func WithCommandShort(short string) CommandOption

WithCommandShort sets the command short description.

func WithCommandUsage

func WithCommandUsage(usage string) CommandOption

WithCommandUsage sets the command usage string.

type ConfigManager

type ConfigManager struct {
	// contains filtered or unexported fields
}

ConfigManager loads and stores configuration from YAML files and environment variables. Configuration values are automatically loaded when App.Run is called and are accessible via Context getters with precedence: command flags > app flags > env > config > defaults.

Example:

app := clix.NewApp("myapp")
// Config is automatically created and loaded
// Access values via context:
cmd.Run = func(ctx *clix.Context) error {
	if value, ok := ctx.String("key"); ok {
		// Value from config file, env var, or default
	}
	return nil
}

func NewConfigManager

func NewConfigManager(name string) *ConfigManager

NewConfigManager constructs a manager for the given application name.

func (*ConfigManager) Bool

func (m *ConfigManager) Bool(key string) (bool, bool)

Bool retrieves a boolean value from persisted config.

func (*ConfigManager) Delete

func (m *ConfigManager) Delete(key string) bool

Delete removes a key from the configuration. It returns true if the key existed. Keys are stored using dot-separated paths (e.g. "project.default").

func (*ConfigManager) Float64

func (m *ConfigManager) Float64(key string) (float64, bool)

Float64 retrieves a float64 value from persisted config.

func (*ConfigManager) Get

func (m *ConfigManager) Get(key string) (string, bool)

Get retrieves a value.

func (*ConfigManager) Int64

func (m *ConfigManager) Int64(key string) (int64, bool)

Int64 retrieves an int64 value from persisted config.

func (*ConfigManager) Integer

func (m *ConfigManager) Integer(key string) (int, bool)

Integer retrieves an int value from persisted config.

func (*ConfigManager) Load

func (m *ConfigManager) Load(path string) error

Load reads configuration from the provided path. Missing files are ignored.

func (*ConfigManager) NormalizeValue

func (m *ConfigManager) NormalizeValue(key, value string) (string, error)

NormalizeValue validates and canonicalises a value according to the schema (if present). The returned string is safe to persist. When no schema exists, the original value is returned.

func (*ConfigManager) RegisterSchema

func (m *ConfigManager) RegisterSchema(entries ...ConfigSchemaOption)

RegisterSchema registers one or more schema entries for configuration keys. Registration is optional; keys without schema entries behave like raw strings. Accepts either ConfigSchema structs (primary API) or functional options (convenience layer).

Example - two API styles:

// 1. Struct-based (primary API)
app.Config.RegisterSchema(clix.ConfigSchema{
	Key:  "project.retries",
	Type: clix.ConfigInteger,
	Validate: validation.IntRange(1, 10),
})

// 2. Functional options
app.Config.RegisterSchema(
	clix.WithConfigKey("project.retries"),
	clix.WithConfigType(clix.ConfigInteger),
	clix.WithConfigValidate(validation.IntRange(1, 10)),
)

// 3. Mixed (struct + functional options)
app.Config.RegisterSchema(
	clix.ConfigSchema{Key: "project.retries"},
	clix.WithConfigType(clix.ConfigInteger),
)

func (*ConfigManager) Reset

func (m *ConfigManager) Reset()

Reset removes all values.

func (*ConfigManager) Save

func (m *ConfigManager) Save(path string) error

Save writes the configuration to the provided path in YAML format.

func (*ConfigManager) Set

func (m *ConfigManager) Set(key, value string)

Set stores a value.

func (*ConfigManager) String

func (m *ConfigManager) String(key string) (string, bool)

String retrieves a raw string value directly from persisted config.

func (*ConfigManager) Values

func (m *ConfigManager) Values() map[string]string

Values returns a copy of the stored values.

type ConfigSchema

type ConfigSchema struct {
	Key      string
	Type     ConfigType
	Validate func(string) error
}

ConfigSchema describes an expected type (and optional validator) for a config key. This struct implements ConfigSchemaOption, so it can be used alongside functional options. This is optional— schemas only apply when registered via RegisterSchema.

Example:

// Struct-based (primary API)
app.Config.RegisterSchema(clix.ConfigSchema{
	Key:  "project.retries",
	Type: clix.ConfigInteger,
	Validate: validation.IntRange(1, 10),
})

// Functional options
app.Config.RegisterSchema(
	WithConfigKey("project.retries"),
	WithConfigType(clix.ConfigInteger),
	WithConfigValidate(validation.IntRange(1, 10)),
)

func (ConfigSchema) ApplyConfigSchema

func (s ConfigSchema) ApplyConfigSchema(schema *ConfigSchema)

ApplyConfigSchema implements ConfigSchemaOption so ConfigSchema can be used directly.

func (*ConfigSchema) SetKey

func (s *ConfigSchema) SetKey(key string) *ConfigSchema

SetKey sets the config schema key and returns the schema for method chaining.

func (*ConfigSchema) SetType

func (s *ConfigSchema) SetType(typ ConfigType) *ConfigSchema

SetType sets the config schema type and returns the schema for method chaining.

func (*ConfigSchema) SetValidate

func (s *ConfigSchema) SetValidate(validate func(string) error) *ConfigSchema

SetValidate sets the validation function and returns the schema for method chaining.

type ConfigSchemaOption

type ConfigSchemaOption interface {
	// ApplyConfigSchema configures a config schema struct.
	// Exported so extension packages can implement ConfigSchemaOption.
	ApplyConfigSchema(*ConfigSchema)
}

ConfigSchemaOption configures a config schema using the functional options pattern. Options can be used to build schemas:

// Using functional options
app.Config.RegisterSchema(
	WithConfigKey("project.retries"),
	WithConfigType(clix.ConfigInteger),
	WithConfigValidate(validation.IntRange(1, 10)),
)

// Using struct (primary API)
app.Config.RegisterSchema(clix.ConfigSchema{
	Key:  "project.retries",
	Type: clix.ConfigInteger,
	Validate: validation.IntRange(1, 10),
})

func WithConfigKey

func WithConfigKey(key string) ConfigSchemaOption

WithConfigKey sets the config schema key.

func WithConfigType

func WithConfigType(typ ConfigType) ConfigSchemaOption

WithConfigType sets the config schema type.

func WithConfigValidate

func WithConfigValidate(validate func(string) error) ConfigSchemaOption

WithConfigValidate sets the config schema validation function.

type ConfigType

type ConfigType int

ConfigType represents the desired type for a configuration value.

const (
	// ConfigString stores raw string values (default behaviour).
	ConfigString ConfigType = iota
	// ConfigBool stores canonical boolean values ("true"/"false").
	ConfigBool
	// ConfigInteger stores 32-bit integers.
	ConfigInteger
	// ConfigInt64 stores 64-bit integers.
	ConfigInt64
	// ConfigFloat64 stores floating-point numbers.
	ConfigFloat64
)

type Context

type Context struct {
	context.Context // Embedded for cancellation, deadlines, and context values

	// App is the application instance executing this command.
	App *App

	// Command is the currently executing command.
	Command *Command

	// Args contains positional arguments passed to the command.
	// Use Arg(index) or ArgNamed(name) for safer access with bounds checking.
	Args []string
}

Context is passed to command handlers and provides convenient access to the resolved command, arguments, configuration and flags. Context provides CLI-specific context for command execution. It embeds context.Context for cancellation and deadlines, and adds CLI-specific data like the active command, arguments, and app instance.

Context is passed to all command handlers (Run, PreRun, PostRun) and provides access to flags, arguments, and configuration via type-specific getters that respect precedence: command flags > app flags > env > config > defaults.

Example:

cmd.Run = func(ctx *clix.Context) error {
	// Access flags with precedence
	if project, ok := ctx.String("project"); ok {
		fmt.Printf("Using project: %s\n", project)
	}

	// Access arguments
	if name, ok := ctx.ArgNamed("name"); ok {
		fmt.Printf("Hello, %s!\n", name)
	}

	// Use context.Context for cancellation
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		// Continue execution
	}
	return nil
}

Context wraps the standard library context.Context with CLI metadata. It should be passed to any code that needs cancellation, deadlines, or values related to this command execution.

Because Context embeds context.Context, you can pass it anywhere a context.Context is required (e.g., to Prompter.Prompt).

func (*Context) AllArgs

func (ctx *Context) AllArgs() []string

AllArgs returns all positional arguments as a slice. This provides a symmetric API with String()/Bool() for flags. You can also access ctx.Args directly if preferred.

func (*Context) Arg

func (ctx *Context) Arg(index int) string

Arg returns the positional argument at the given index. Returns empty string if index is out of bounds.

func (*Context) ArgNamed

func (ctx *Context) ArgNamed(name string) (string, bool)

ArgNamed returns the value of a named argument by its name. Returns the value and true if found, empty string and false otherwise. This looks up arguments by the Name field in the command's Arguments definition.

func (*Context) Bool

func (ctx *Context) Bool(key string) (bool, bool)

Bool retrieves a boolean configuration value using the same precedence as String (command flags, root flags, env, config, defaults). This follows the log/slog naming pattern for type-specific getters. Precedence: command flags > app flags > env > config > defaults

Example

ExampleContext_Bool demonstrates how to access boolean configuration values.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")
	root := clix.NewCommand("myapp")
	app.Root = root

	var verbose bool
	app.Flags().BoolVar(clix.BoolVarOptions{
		FlagOptions: clix.FlagOptions{
			Name: "verbose",
		},
		Value: &verbose,
	})

	root.Run = func(ctx *clix.Context) error {
		if verbose, ok := ctx.Bool("verbose"); ok && verbose {
			fmt.Fprintln(ctx.App.Out, "Verbose mode enabled")
		}

		return nil
	}
}

func (*Context) String

func (ctx *Context) String(key string) (string, bool)

String retrieves a string configuration value with the given key, looking at command flags, root flags, environment variables, config file, then defaults. This follows the log/slog naming pattern for type-specific getters. Precedence: command flags > app flags > env > config > defaults

Example

ExampleContext_String demonstrates how to access configuration values from context.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")
	root := clix.NewCommand("myapp")
	app.Root = root

	var project string
	app.Flags().StringVar(clix.StringVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:   "project",
			EnvVar: "MYAPP_PROJECT",
		},
		Value: &project,
	})

	root.Run = func(ctx *clix.Context) error {
		// String checks flags, env vars, and config in precedence order
		if project, ok := ctx.String("project"); ok {
			fmt.Fprintf(ctx.App.Out, "Using project: %s\n", project)
		} else {
			fmt.Fprintln(ctx.App.Out, "No project specified")
		}

		return nil
	}
}

type DurationValue

type DurationValue struct {
	// contains filtered or unexported fields
}

DurationValue implements Value using Go's duration parser.

func (*DurationValue) Set

func (d *DurationValue) Set(value string) error

func (*DurationValue) String

func (d *DurationValue) String() string

type DurationVarOptions

type DurationVarOptions struct {
	FlagOptions
	// Default is the default value as a duration string (e.g., "30s", "5m").
	Default string
	// Value is a pointer to the variable that will store the flag value.
	Value *time.Duration
}

DurationVarOptions describes the configuration for adding a duration flag. This struct implements FlagOption, so it can be used alongside functional options.

Example:

var timeout time.Duration
// Struct-based (primary API)
cmd.Flags.DurationVar(clix.DurationVarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "timeout",
		Usage: "Operation timeout",
	},
	Default: "30s",
	Value: &timeout,
})

// Functional options
cmd.Flags.DurationVar(
	WithFlagName("timeout"),
	WithFlagUsage("Operation timeout"),
	WithDurationValue(&timeout),
	WithDurationDefault("30s"),
)

func (DurationVarOptions) ApplyFlag

func (o DurationVarOptions) ApplyFlag(fo *FlagOptions)

ApplyFlag implements FlagOption so DurationVarOptions can be used directly.

type Extension

type Extension interface {
	// Extend is called during app initialization to register commands, hooks,
	// or modify app behavior. Extensions are applied in the order they are added.
	Extend(app *App) error
}

Extension is the interface that optional "batteries-included" features implement. Extensions allow features to be added to an App without requiring imports in the core package, keeping simple applications lightweight.

This design is inspired by goldmark's extension system: https://github.com/yuin/goldmark

Example:

type MyExtension struct{}

func (e MyExtension) Extend(app *clix.App) error {
	if app.Root != nil {
		cmd := clix.NewCommand("custom")
		cmd.Short = "Custom command"
		app.Root.AddCommand(cmd)
	}
	return nil
}

app.AddExtension(MyExtension{})
Example

ExampleExtension demonstrates how to create and use extensions. Extensions implement the clix.Extension interface to add optional features.

package main

import (
	"github.com/SCKelemen/clix"
)

func main() {
	// Define a custom extension type
	type MyExtension struct {
		FeatureEnabled bool
	}

	// Implement the Extension interface
	var ext clix.Extension = extensionImpl{enabled: true}

	// Use the extension
	app := clix.NewApp("myapp")
	app.Root = clix.NewCommand("myapp")
	app.AddExtension(ext)

	// Extensions are applied when app.Run() is called
	// They can add commands, modify behavior, etc.
	_ = MyExtension{FeatureEnabled: true}
}

// extensionImpl is a helper type for the example
type extensionImpl struct {
	enabled bool
}

func (e extensionImpl) Extend(app *clix.App) error {
	return nil
}

type Flag

type Flag struct {
	// Name is the long flag name (e.g., "project" for --project).
	Name string

	// Short is the shorthand flag name (e.g., "p" for -p).
	Short string

	// Usage is the help text shown for this flag.
	Usage string

	// EnvVar is the environment variable name for this flag.
	// If empty, defaults to APP_KEY format based on EnvPrefix.
	EnvVar string

	// Default is the default value for this flag (as a string).
	Default string

	// Value is the flag value implementation.
	Value Value
	// contains filtered or unexported fields
}

Flag describes a single CLI flag. Flags are created via FlagSet methods (StringVar, BoolVar, etc.).

type FlagOption

type FlagOption interface {
	// ApplyFlag configures a flag option struct.
	// Exported so extension packages can implement FlagOption.
	ApplyFlag(*FlagOptions)
}

FlagOption configures a flag using the functional options pattern. Options can be used to build flags:

// Using functional options
app.Flags().StringVar(WithFlagName("project"), WithFlagShort("p"), WithFlagUsage("Project name"), WithFlagValue(&project))

// Using struct (primary API)
app.Flags().StringVar(clix.StringVarOptions{...})

func WithBoolValue

func WithBoolValue(value *bool) FlagOption

WithBoolValue sets the bool flag value pointer.

func WithDurationDefault

func WithDurationDefault(defaultValue string) FlagOption

WithDurationDefault sets the duration flag default value.

func WithDurationValue

func WithDurationValue(value *time.Duration) FlagOption

WithDurationValue sets the duration flag value pointer.

func WithFlagEnvVar

func WithFlagEnvVar(envVar string) FlagOption

WithFlagEnvVar sets the flag environment variable name.

func WithFlagEnvVars

func WithFlagEnvVars(envVars ...string) FlagOption

WithFlagEnvVars sets additional environment variable aliases.

func WithFlagName

func WithFlagName(name string) FlagOption

WithFlagName sets the flag name.

func WithFlagShort

func WithFlagShort(short string) FlagOption

WithFlagShort sets the flag shorthand.

func WithFlagUsage

func WithFlagUsage(usage string) FlagOption

WithFlagUsage sets the flag usage text.

func WithFloat64Default

func WithFloat64Default(defaultValue string) FlagOption

WithFloat64Default sets the float64 flag default value.

func WithFloat64Value

func WithFloat64Value(value *float64) FlagOption

WithFloat64Value sets the float64 flag value pointer.

func WithInt64Default

func WithInt64Default(defaultValue string) FlagOption

WithInt64Default sets the int64 flag default value.

func WithInt64Value

func WithInt64Value(value *int64) FlagOption

WithInt64Value sets the int64 flag value pointer.

func WithIntegerDefault

func WithIntegerDefault(defaultValue string) FlagOption

WithIntegerDefault sets the integer flag default value.

func WithIntegerValue

func WithIntegerValue(value *int) FlagOption

WithIntegerValue sets the integer flag value pointer.

func WithStringDefault

func WithStringDefault(defaultValue string) FlagOption

WithStringDefault sets the string flag default value.

func WithStringValue

func WithStringValue(value *string) FlagOption

WithStringValue sets the string flag value pointer.

type FlagOptions

type FlagOptions struct {
	// Name is the long flag name (e.g., "project" for --project).
	Name string

	// Short is the optional shorthand flag name (e.g., "p" for -p).
	Short string

	// Usage is the help text shown for this flag.
	Usage string

	// EnvVar is the environment variable name for this flag.
	// If empty, defaults to APP_KEY format based on the app's EnvPrefix.
	EnvVar string

	// EnvVars are optional additional environment variable aliases.
	// All listed environment variables are checked in order.
	EnvVars []string
}

FlagOptions contains common configuration for all flag types. This struct is embedded in all *VarOptions types to provide a unified API.

type FlagSet

type FlagSet struct {
	// contains filtered or unexported fields
}

FlagSet stores a collection of flags for a command or scope. Flags defined on the root command (via app.Flags()) apply to all commands. Flags defined on a command are scoped to that command.

Example:

// App-level flags (global)
app.Flags().StringVar(clix.StringVarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "project",
		Short: "p",
		Usage: "Project to operate on",
		EnvVar: "MYAPP_PROJECT",
	},
	Default: "default-project",
	Value: &project,
})

// Command-level flags
cmd.Flags.BoolVar(clix.BoolVarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "verbose",
		Short: "v",
		Usage: "Enable verbose output",
	},
	Value: &verbose,
})

func NewFlagSet

func NewFlagSet(name string) *FlagSet

NewFlagSet initialises an empty flag set.

func (*FlagSet) Bool

func (fs *FlagSet) Bool(name string) (bool, bool)

Bool fetches a boolean flag value.

func (*FlagSet) BoolVar

func (fs *FlagSet) BoolVar(opts ...FlagOption)

BoolVar registers a boolean flag. Accepts either a BoolVarOptions struct (primary API) or functional options (convenience layer).

Example

ExampleFlagSet_BoolVar demonstrates how to register boolean flags.

package main

import (
	"github.com/SCKelemen/clix"
)

func main() {
	var verbose bool
	var force bool

	app := clix.NewApp("myapp")
	root := clix.NewCommand("myapp")
	app.Root = root

	// Global boolean flag
	app.Flags().BoolVar(clix.BoolVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:  "verbose",
			Short: "v",
			Usage: "Enable verbose output",
		},
		Value: &verbose,
	})

	// Command-level boolean flag
	root.Flags.BoolVar(clix.BoolVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:  "force",
			Short: "f",
			Usage: "Force operation without confirmation",
		},
		Value: &force,
	})
}

func (*FlagSet) DurationVar

func (fs *FlagSet) DurationVar(opts ...FlagOption)

DurationVar registers a duration flag. Accepts either a DurationVarOptions struct (primary API) or functional options (convenience layer).

func (*FlagSet) Flags

func (fs *FlagSet) Flags() []*Flag

Flags returns all registered flags.

func (*FlagSet) Float64

func (fs *FlagSet) Float64(name string) (float64, bool)

Float64 fetches a float64 flag value.

func (*FlagSet) Float64Var

func (fs *FlagSet) Float64Var(opts ...FlagOption)

Float64Var registers a float64 flag. Accepts either a Float64VarOptions struct (primary API) or functional options (convenience layer).

func (*FlagSet) Int64

func (fs *FlagSet) Int64(name string) (int64, bool)

Int64 fetches an int64 flag value.

func (*FlagSet) Int64Var

func (fs *FlagSet) Int64Var(opts ...FlagOption)

Int64Var registers an int64 flag. Accepts either an Int64VarOptions struct (primary API) or functional options (convenience layer).

func (*FlagSet) IntVar

func (fs *FlagSet) IntVar(opts ...FlagOption)

IntVar registers an int flag. Accepts either an IntVarOptions struct (primary API) or functional options (convenience layer).

func (*FlagSet) Integer

func (fs *FlagSet) Integer(name string) (int, bool)

Integer fetches an int flag value.

func (*FlagSet) Parse

func (fs *FlagSet) Parse(args []string) ([]string, error)

Parse consumes recognised flags from args, returning remaining positional arguments.

func (*FlagSet) String

func (fs *FlagSet) String(name string) (string, bool)

String fetches a string flag value.

func (*FlagSet) StringVar

func (fs *FlagSet) StringVar(opts ...FlagOption)

StringVar registers a string flag. Accepts either a StringVarOptions struct (primary API) or functional options (convenience layer).

// Struct-based (primary API)
app.Flags().StringVar(clix.StringVarOptions{
	FlagOptions: clix.FlagOptions{Name: "project", Short: "p"},
	Value: &project,
})

// Functional options
app.Flags().StringVar(
	WithFlagName("project"),
	WithFlagShort("p"),
	WithStringValue(&project),
)
Example

ExampleFlagSet_StringVar demonstrates how to register string flags.

package main

import (
	"github.com/SCKelemen/clix"
)

func main() {
	var project string
	var region string

	app := clix.NewApp("myapp")
	root := clix.NewCommand("myapp")
	app.Root = root

	// Global flags
	app.Flags().StringVar(clix.StringVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:   "project",
			Short:  "p",
			Usage:  "Project to operate on",
			EnvVar: "MYAPP_PROJECT",
		},
		Value:   &project,
		Default: "default-project",
	})

	// Command-level flags
	root.Flags.StringVar(clix.StringVarOptions{
		FlagOptions: clix.FlagOptions{
			Name:   "region",
			Short:  "r",
			Usage:  "Region to deploy to",
			EnvVar: "MYAPP_REGION",
		},
		Value:   &region,
		Default: "us-east-1",
	})
}

type Float64Value

type Float64Value struct {
	// contains filtered or unexported fields
}

Float64Value implements Value for float64 flags.

func (*Float64Value) Set

func (f *Float64Value) Set(value string) error

func (*Float64Value) String

func (f *Float64Value) String() string

type Float64VarOptions

type Float64VarOptions struct {
	FlagOptions
	// Default is the default value as a string (e.g., "0.5").
	Default string
	// Value is a pointer to the variable that will store the flag value.
	Value *float64
}

Float64VarOptions describes the configuration for adding a float64 flag. This struct implements FlagOption, so it can be used alongside functional options.

Example:

var ratio float64
// Struct-based (primary API)
cmd.Flags.Float64Var(clix.Float64VarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "ratio",
		Usage: "Compression ratio",
	},
	Default: "0.5",
	Value: &ratio,
})

// Functional options
cmd.Flags.Float64Var(
	WithFlagName("ratio"),
	WithFlagUsage("Compression ratio"),
	WithFloat64Value(&ratio),
	WithFloat64Default("0.5"),
)

func (Float64VarOptions) ApplyFlag

func (o Float64VarOptions) ApplyFlag(fo *FlagOptions)

ApplyFlag implements FlagOption so Float64VarOptions can be used directly.

type Handler

type Handler func(ctx *Context) error

Handler is the function signature for executing a command.

type HelpRenderer

type HelpRenderer struct {
	App     *App
	Command *Command
}

HelpRenderer prints help text for commands.

func (HelpRenderer) Render

func (h HelpRenderer) Render(w io.Writer) error

Render writes the help to the provided writer.

type Hook

type Hook func(ctx *Context) error

Hook is executed before or after the main handler.

type Int64Value

type Int64Value struct {
	// contains filtered or unexported fields
}

Int64Value implements Value for int64 flags.

func (*Int64Value) Set

func (i *Int64Value) Set(value string) error

func (*Int64Value) String

func (i *Int64Value) String() string

type Int64VarOptions

type Int64VarOptions struct {
	FlagOptions
	// Default is the default value as a string (e.g., "1024").
	Default string
	// Value is a pointer to the variable that will store the flag value.
	Value *int64
}

Int64VarOptions describes the configuration for adding an int64 flag. This struct implements FlagOption, so it can be used alongside functional options.

Example:

var size int64
// Struct-based (primary API)
cmd.Flags.Int64Var(clix.Int64VarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "size",
		Usage: "Size in bytes",
	},
	Default: "1024",
	Value: &size,
})

// Functional options
cmd.Flags.Int64Var(
	WithFlagName("size"),
	WithFlagUsage("Size in bytes"),
	WithInt64Value(&size),
	WithInt64Default("1024"),
)

func (Int64VarOptions) ApplyFlag

func (o Int64VarOptions) ApplyFlag(fo *FlagOptions)

ApplyFlag implements FlagOption so Int64VarOptions can be used directly.

type IntValue

type IntValue struct {
	// contains filtered or unexported fields
}

IntValue implements Value for int flags.

func (*IntValue) Set

func (i *IntValue) Set(value string) error

func (*IntValue) String

func (i *IntValue) String() string

type IntVarOptions

type IntVarOptions struct {
	FlagOptions
	// Default is the default value as a string (e.g., "8080").
	Default string
	// Value is a pointer to the variable that will store the flag value.
	Value *int
}

IntVarOptions describes the configuration for adding an int flag. This struct implements FlagOption, so it can be used alongside functional options.

Example:

var port int
// Struct-based (primary API)
cmd.Flags.IntVar(clix.IntVarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "port",
		Usage: "Server port",
	},
	Default: "8080",
	Value: &port,
})

// Functional options
cmd.Flags.IntVar(
	WithFlagName("port"),
	WithFlagUsage("Server port"),
	WithIntegerValue(&port),
	WithIntegerDefault("8080"),
)

func (IntVarOptions) ApplyFlag

func (o IntVarOptions) ApplyFlag(fo *FlagOptions)

ApplyFlag implements FlagOption so IntVarOptions can be used directly.

func (*IntVarOptions) SetDefault

func (o *IntVarOptions) SetDefault(defaultValue string) *IntVarOptions

SetDefault sets the default value and returns the options for method chaining.

func (*IntVarOptions) SetEnvVar

func (o *IntVarOptions) SetEnvVar(envVar string) *IntVarOptions

SetEnvVar sets the environment variable name and returns the options for method chaining.

func (*IntVarOptions) SetName

func (o *IntVarOptions) SetName(name string) *IntVarOptions

SetName sets the flag name and returns the options for method chaining.

func (*IntVarOptions) SetShort

func (o *IntVarOptions) SetShort(short string) *IntVarOptions

SetShort sets the flag shorthand and returns the options for method chaining.

func (*IntVarOptions) SetUsage

func (o *IntVarOptions) SetUsage(usage string) *IntVarOptions

SetUsage sets the flag usage text and returns the options for method chaining.

func (*IntVarOptions) SetValue

func (o *IntVarOptions) SetValue(value *int) *IntVarOptions

SetValue sets the value pointer and returns the options for method chaining.

type PromptButtonStyles

type PromptButtonStyles struct {
	// Active styles active button hints.
	Active TextStyle

	// Inactive styles inactive/grayed-out button hints.
	Inactive TextStyle

	// Hover styles hovered button hints.
	Hover TextStyle
}

PromptButtonStyles groups button hint styles together for easier configuration. These styles are used for keyboard shortcut hints in prompts.

type PromptCommand

type PromptCommand struct {
	Type        PromptCommandType
	FunctionKey int
}

PromptCommand describes a high-level command initiated by the user. For function keys, FunctionKey contains the key index (1-12).

type PromptCommandAction

type PromptCommandAction struct {
	// Handled indicates the command was consumed and default handling should be skipped.
	Handled bool
	// Exit requests the prompter to exit immediately.
	// If ExitErr is non-nil it will be returned from the prompt.
	Exit bool
	// ExitErr is returned from the prompt when Exit is true.
	ExitErr error
}

PromptCommandAction instructs the prompter how to proceed after a command is handled.

type PromptCommandContext

type PromptCommandContext struct {
	PromptKeyState
	SetInput func(string)
}

PromptCommandContext provides the handler context when a key binding is invoked.

type PromptCommandHandler

type PromptCommandHandler func(PromptCommandContext) PromptCommandAction

PromptCommandHandler processes special key commands during an interactive prompt. Returning an action with Exit=true stops the prompt immediately.

type PromptCommandType

type PromptCommandType int

PromptCommandType identifies a special key command intercepted by interactive prompts.

const (
	// PromptCommandUnknown represents an unclassified key.
	PromptCommandUnknown PromptCommandType = iota
	// PromptCommandEscape indicates the escape key was pressed.
	PromptCommandEscape
	// PromptCommandTab indicates the tab key was pressed.
	PromptCommandTab
	// PromptCommandFunction indicates an F-key (F1-F12) was pressed.
	PromptCommandFunction
	// PromptCommandEnter indicates the enter key was pressed.
	PromptCommandEnter
)

type PromptConfig

type PromptConfig struct {
	Label                string
	Default              string
	NoDefaultPlaceholder string
	Validate             func(string) error
	Theme                PromptTheme
	Options              []SelectOption
	MultiSelect          bool
	Confirm              bool
	ContinueText         string
	CommandHandler       PromptCommandHandler
	KeyMap               PromptKeyMap
}

PromptConfig holds all prompt configuration internally. Exported so extension packages can implement PromptOption.

type PromptKeyBinding

type PromptKeyBinding struct {
	Command     PromptCommand
	Description string
	Handler     PromptCommandHandler
	Active      func(PromptKeyState) bool
}

PromptKeyBinding maps a command to display metadata and optional handling.

type PromptKeyMap

type PromptKeyMap struct {
	Bindings []PromptKeyBinding
}

PromptKeyMap holds the configured key bindings for a prompt.

func (PromptKeyMap) BindingFor

func (m PromptKeyMap) BindingFor(cmd PromptCommand) (PromptKeyBinding, bool)

BindingFor returns the configured binding for the given command, if any.

type PromptKeyState

type PromptKeyState struct {
	Command    PromptCommand
	Input      string
	Default    string
	Suggestion string
}

PromptKeyState describes the prompt state when evaluating key bindings.

type PromptOption

type PromptOption interface {
	// Apply configures the prompt config.
	// Exported so extension packages can implement PromptOption.
	Apply(*PromptConfig)
}

PromptOption configures a prompt using the functional options pattern. Options can be used to build prompts:

// Basic text prompt
prompter.Prompt(ctx, WithLabel("Name"), WithDefault("unknown"))

// Advanced prompts (require prompt extension):
// prompter.Prompt(ctx, WithLabel("Choose"), Select([]SelectOption{...}))

func WithCommandHandler

func WithCommandHandler(handler PromptCommandHandler) PromptOption

WithCommandHandler registers a handler for special key commands during prompts. Command handlers can intercept escape, tab, function keys, and enter to provide custom behavior (e.g., autocomplete, help, cancellation).

Example:

result, err := prompter.Prompt(ctx,
	clix.WithLabel("Enter value"),
	clix.WithCommandHandler(func(ctx clix.PromptCommandContext) clix.PromptCommandAction {
		if ctx.Command.Type == clix.PromptCommandEscape {
			return clix.PromptCommandAction{Exit: true, ExitErr: errors.New("cancelled")}
		}
		return clix.PromptCommandAction{Handled: false}
	}),
)

func WithConfirm

func WithConfirm() PromptOption

WithConfirm enables yes/no confirmation prompt mode (functional option). Works with TextPrompter - it's just a text prompt with y/n validation. Returns "y" or "n" (or "yes"/"no").

Example:

result, err := prompter.Prompt(ctx,
	clix.WithLabel("Continue?"),
	clix.WithConfirm(),
)
if result == "y" {
	// User confirmed
}

func WithDefault

func WithDefault(def string) PromptOption

WithDefault sets the default value (functional option). The default is shown in the prompt and used if the user presses Enter without input.

Example:

result, err := prompter.Prompt(ctx,
	clix.WithLabel("Color"),
	clix.WithDefault("blue"),
)

func WithKeyMap

func WithKeyMap(m PromptKeyMap) PromptOption

WithKeyMap configures the key bindings shown and invoked by the prompt. Key maps allow you to define keyboard shortcuts with descriptions and handlers.

Example:

keyMap := clix.PromptKeyMap{
	Bindings: []clix.PromptKeyBinding{
		{
			Command:     clix.PromptCommand{Type: clix.PromptCommandEscape},
			Description: "Cancel",
			Handler: func(ctx clix.PromptCommandContext) clix.PromptCommandAction {
				return clix.PromptCommandAction{Exit: true}
			},
		},
	},
}
result, err := prompter.Prompt(ctx,
	clix.WithLabel("Enter value"),
	clix.WithKeyMap(keyMap),
)

func WithLabel

func WithLabel(label string) PromptOption

WithLabel sets the prompt label (functional option).

Example:

result, err := prompter.Prompt(ctx,
	clix.WithLabel("Enter your name"),
)
Example

ExampleWithLabel demonstrates how to use the functional options API for prompts.

package main

import (
	"fmt"
	"strings"

	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")
	root := clix.NewCommand(app.Name)
	app.Root = root

	root.Run = func(ctx *clix.Context) error {
		// Basic text prompt using functional options
		name, err := ctx.App.Prompter.Prompt(ctx,
			clix.WithLabel("Enter your name"),
			clix.WithDefault("Anonymous"),
		)
		if err != nil {
			return err
		}

		// Prompt with validation using functional options
		email, err := ctx.App.Prompter.Prompt(ctx,
			clix.WithLabel("Enter your email"),
			clix.WithValidate(func(value string) error {
				if !strings.Contains(value, "@") {
					return fmt.Errorf("invalid email address")
				}
				return nil
			}),
		)
		if err != nil {
			return err
		}

		// Confirm prompt using functional options
		confirmed, err := ctx.App.Prompter.Prompt(ctx,
			clix.WithLabel("Continue?"),
			clix.WithConfirm(),
		)
		if err != nil {
			return err
		}

		if confirmed == "yes" {
			fmt.Fprintf(ctx.App.Out, "Hello %s (%s)!\n", name, email)
		}

		return nil
	}
}

func WithNoDefaultPlaceholder

func WithNoDefaultPlaceholder(text string) PromptOption

WithNoDefaultPlaceholder sets the placeholder text shown when no default exists. This is typically used by higher-level workflows (like surveys) to prompt the user that pressing enter will keep their existing value.

Example:

result, err := prompter.Prompt(ctx,
	clix.WithLabel("Enter value"),
	clix.WithNoDefaultPlaceholder("(press Enter to keep current value)"),
)

func WithTheme

func WithTheme(theme PromptTheme) PromptOption

WithTheme sets the prompt theme (functional option). Themes control the visual appearance of prompts (prefix, hint, error indicators, styling). If not set, uses app.DefaultTheme.

Example:

theme := clix.PromptTheme{
	Prefix: "> ",
	Error:  "✗ ",
}
result, err := prompter.Prompt(ctx,
	clix.WithLabel("Name"),
	clix.WithTheme(theme),
)

func WithValidate

func WithValidate(validate func(string) error) PromptOption

WithValidate sets the validation function (functional option). The validation function is called when the user submits input. Return an error if the value is invalid; the prompt will re-prompt until valid.

Example:

result, err := prompter.Prompt(ctx,
	clix.WithLabel("Enter email"),
	clix.WithValidate(func(value string) error {
		if !strings.Contains(value, "@") {
			return errors.New("invalid email address")
		}
		return nil
	}),
)

type PromptRequest

type PromptRequest struct {
	// Label is the prompt text shown to the user.
	Label string

	// Default is the default value shown in the prompt.
	// For text prompts, this appears as placeholder text.
	Default string

	// NoDefaultPlaceholder is custom placeholder text when Default is empty.
	NoDefaultPlaceholder string

	// Validate is an optional validation function called when the user submits input.
	// Return an error if the value is invalid.
	Validate func(string) error

	// Theme configures the styling for this prompt.
	// If not set, uses app.DefaultTheme.
	Theme PromptTheme

	// Options are the choices for select/multi-select prompts.
	// Requires the prompt extension for advanced prompt types.
	Options []SelectOption

	// MultiSelect enables multi-select mode (user can choose multiple options).
	// Requires the prompt extension.
	MultiSelect bool

	// Confirm enables confirm mode (yes/no prompt).
	// Returns "y" or "n" (or "yes"/"no").
	Confirm bool

	// ContinueText is the text shown for the continue button in select prompts.
	ContinueText string

	// CommandHandler allows custom command handling during prompts.
	// Users can type commands that are processed by this handler.
	CommandHandler PromptCommandHandler

	// KeyMap configures keyboard shortcuts for the prompt.
	KeyMap PromptKeyMap
}

PromptRequest is the struct-based API for prompts, consistent with the rest of the codebase. This is the primary API - functional options are a convenience layer.

Example:

// Basic text prompt
result, err := app.Prompter.Prompt(ctx, clix.PromptRequest{
	Label:   "Name",
	Default: "unknown",
	Validate: validation.NotEmpty,
})

// Select prompt (requires prompt extension)
result, err := app.Prompter.Prompt(ctx, clix.PromptRequest{
	Label: "Choose an option",
	Options: []clix.SelectOption{
		{Label: "Option A", Value: "a"},
		{Label: "Option B", Value: "b"},
	},
})

// Confirm prompt
result, err := app.Prompter.Prompt(ctx, clix.PromptRequest{
	Label:   "Continue?",
	Confirm: true,
})
Example

ExamplePromptRequest demonstrates how to use the struct-based prompt API.

package main

import (
	"fmt"
	"strings"

	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")
	root := clix.NewCommand("myapp")
	app.Root = root

	root.Run = func(ctx *clix.Context) error {
		// Basic text prompt
		name, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
			Label:   "Enter your name",
			Default: "Anonymous",
		})
		if err != nil {
			return err
		}

		// Prompt with validation
		email, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
			Label: "Enter your email",
			Validate: func(value string) error {
				if !strings.Contains(value, "@") {
					return fmt.Errorf("invalid email address")
				}
				return nil
			},
		})
		if err != nil {
			return err
		}

		// Confirm prompt
		confirmed, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{
			Label:   "Continue?",
			Confirm: true,
		})
		if err != nil {
			return err
		}

		if confirmed == "yes" {
			fmt.Fprintf(ctx.App.Out, "Hello %s (%s)!\n", name, email)
		}

		return nil
	}
}

func (PromptRequest) Apply

func (r PromptRequest) Apply(cfg *PromptConfig)

Apply implements PromptOption so PromptRequest can be used directly.

func (*PromptRequest) SetCommandHandler

func (r *PromptRequest) SetCommandHandler(handler PromptCommandHandler) *PromptRequest

SetCommandHandler sets the command handler and returns the request for method chaining.

func (*PromptRequest) SetConfirm

func (r *PromptRequest) SetConfirm(confirm bool) *PromptRequest

SetConfirm enables confirm mode and returns the request for method chaining.

func (*PromptRequest) SetContinueText

func (r *PromptRequest) SetContinueText(text string) *PromptRequest

SetContinueText sets the continue text and returns the request for method chaining.

func (*PromptRequest) SetDefault

func (r *PromptRequest) SetDefault(defaultValue string) *PromptRequest

SetDefault sets the prompt default value and returns the request for method chaining.

func (*PromptRequest) SetKeyMap

func (r *PromptRequest) SetKeyMap(keyMap PromptKeyMap) *PromptRequest

SetKeyMap sets the key map and returns the request for method chaining.

func (*PromptRequest) SetLabel

func (r *PromptRequest) SetLabel(label string) *PromptRequest

SetLabel sets the prompt label and returns the request for method chaining.

func (*PromptRequest) SetMultiSelect

func (r *PromptRequest) SetMultiSelect(multiSelect bool) *PromptRequest

SetMultiSelect enables multi-select mode and returns the request for method chaining.

func (*PromptRequest) SetNoDefaultPlaceholder

func (r *PromptRequest) SetNoDefaultPlaceholder(placeholder string) *PromptRequest

SetNoDefaultPlaceholder sets the no-default placeholder and returns the request for method chaining.

func (*PromptRequest) SetOptions

func (r *PromptRequest) SetOptions(options ...SelectOption) *PromptRequest

SetOptions sets the prompt options and returns the request for method chaining.

func (*PromptRequest) SetTheme

func (r *PromptRequest) SetTheme(theme PromptTheme) *PromptRequest

SetTheme sets the prompt theme and returns the request for method chaining.

func (*PromptRequest) SetValidate

func (r *PromptRequest) SetValidate(validate func(string) error) *PromptRequest

SetValidate sets the validation function and returns the request for method chaining.

type PromptTheme

type PromptTheme struct {
	// Prefix is the text shown before the prompt label (e.g., "? ", "> ").
	Prefix string

	// Hint is the text shown as a hint below the prompt (e.g., "(optional)").
	Hint string

	// Error is the text shown before error messages (e.g., "! ", "✗ ").
	Error string

	// PrefixStyle styles the prefix text.
	PrefixStyle TextStyle

	// LabelStyle styles the prompt label text.
	LabelStyle TextStyle

	// HintStyle styles the hint text.
	HintStyle TextStyle

	// DefaultStyle styles the default value text.
	DefaultStyle TextStyle

	// PlaceholderStyle styles placeholder/default text (e.g., bracketed defaults).
	PlaceholderStyle TextStyle

	// SuggestionStyle styles inline suggestion/ghost text.
	SuggestionStyle TextStyle

	// ErrorStyle styles error messages.
	ErrorStyle TextStyle

	// ButtonActiveStyle styles active button hints.
	ButtonActiveStyle TextStyle

	// ButtonInactiveStyle styles inactive/grayed-out button hints.
	ButtonInactiveStyle TextStyle

	// ButtonHoverStyle styles hovered button hints.
	ButtonHoverStyle TextStyle

	// Buttons groups button hint styles together for easier configuration.
	Buttons PromptButtonStyles
}

PromptTheme configures the visual appearance of prompts. Themes control the prefix, hint, error indicators, and styling for all prompt elements.

Example:

theme := clix.PromptTheme{
	Prefix: "> ",
	Hint:   "(press Enter to confirm)",
	Error:  "✗ ",
	LabelStyle: lipgloss.NewStyle().Bold(true),
	ErrorStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("9")),
}
result, err := prompter.Prompt(ctx, clix.PromptRequest{
	Label: "Name",
	Theme: theme,
})

type Prompter

type Prompter interface {
	Prompt(ctx context.Context, opts ...PromptOption) (string, error)
}

Prompter encapsulates interactive prompting. Accepts either functional options or a PromptRequest struct:

// Struct-based (primary API, consistent with rest of codebase)
prompter.Prompt(ctx, PromptRequest{Label: "Name", Default: "unknown"})

// Functional options (convenience layer)
prompter.Prompt(ctx, WithLabel("Name"), WithDefault("unknown"))

type SelectOption

type SelectOption struct {
	// Label is the text displayed to the user for this option.
	Label string

	// Value is the value returned when this option is selected.
	Value string

	// Description is optional additional text shown below the label.
	Description string
}

SelectOption represents a choice in a select or multi-select prompt.

Example:

result, err := prompter.Prompt(ctx, clix.PromptRequest{
	Label: "Choose an option",
	Options: []clix.SelectOption{
		{Label: "Option A", Value: "a", Description: "First option"},
		{Label: "Option B", Value: "b", Description: "Second option"},
	},
})

type StringValue

type StringValue struct {
	// contains filtered or unexported fields
}

StringValue implements Value for string flags.

func (*StringValue) Set

func (s *StringValue) Set(value string) error

func (*StringValue) String

func (s *StringValue) String() string

type StringVarOptions

type StringVarOptions struct {
	FlagOptions
	// Default is the default value if the flag is not provided.
	Default string
	// Value is a pointer to the variable that will store the flag value.
	Value *string
}

StringVarOptions describes the configuration for adding a string flag. This struct implements FlagOption, so it can be used alongside functional options.

Example - three API styles:

var project string

// 1. Struct-based (primary API)
app.Flags().StringVar(clix.StringVarOptions{
	FlagOptions: clix.FlagOptions{
		Name:  "project",
		Short: "p",
		Usage: "Project to operate on",
		EnvVar: "MYAPP_PROJECT",
	},
	Default: "default-project",
	Value: &project,
})

// 2. Functional options
app.Flags().StringVar(
	clix.WithFlagName("project"),
	clix.WithFlagShort("p"),
	clix.WithFlagUsage("Project to operate on"),
	clix.WithFlagEnvVar("MYAPP_PROJECT"),
	clix.WithStringValue(&project),
	clix.WithStringDefault("default-project"),
)

// 3. Mixed (struct + functional options)
app.Flags().StringVar(
	clix.StringVarOptions{Value: &project},
	clix.WithFlagName("project"),
	clix.WithFlagShort("p"),
)

func (StringVarOptions) ApplyFlag

func (o StringVarOptions) ApplyFlag(fo *FlagOptions)

ApplyFlag implements FlagOption so StringVarOptions can be used directly.

func (*StringVarOptions) SetDefault

func (o *StringVarOptions) SetDefault(defaultValue string) *StringVarOptions

SetDefault sets the default value and returns the options for method chaining.

func (*StringVarOptions) SetEnvVar

func (o *StringVarOptions) SetEnvVar(envVar string) *StringVarOptions

SetEnvVar sets the environment variable name and returns the options for method chaining.

func (*StringVarOptions) SetName

func (o *StringVarOptions) SetName(name string) *StringVarOptions

SetName sets the flag name and returns the options for method chaining.

func (*StringVarOptions) SetShort

func (o *StringVarOptions) SetShort(short string) *StringVarOptions

SetShort sets the flag shorthand and returns the options for method chaining.

func (*StringVarOptions) SetUsage

func (o *StringVarOptions) SetUsage(usage string) *StringVarOptions

SetUsage sets the flag usage text and returns the options for method chaining.

func (*StringVarOptions) SetValue

func (o *StringVarOptions) SetValue(value *string) *StringVarOptions

SetValue sets the value pointer and returns the options for method chaining.

type StyleFunc

type StyleFunc func(...string) string

StyleFunc adapts a plain function into a TextStyle. For compatibility with lipgloss.Style, the function accepts variadic strings. When called with a single string, it behaves as expected.

Example

ExampleStyleFunc demonstrates how to use styling with lipgloss compatibility.

package main

import (
	"fmt"

	"github.com/SCKelemen/clix"
)

func main() {
	app := clix.NewApp("myapp")
	root := clix.NewCommand("myapp")
	app.Root = root

	// Create a simple style function
	style := clix.StyleFunc(func(strs ...string) string {
		if len(strs) == 0 {
			return ""
		}
		return fmt.Sprintf(">>> %s <<<", strs[0])
	})

	// Apply to app styles
	app.Styles.SectionHeading = style
	app.Styles.CommandTitle = style

	// StyleFunc is compatible with lipgloss.Style
	// You can use lipgloss styles directly:
	// import "github.com/charmbracelet/lipgloss"
	// lipglossStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
	// app.Styles.SectionHeading = clix.StyleFunc(lipglossStyle.Render)
}

func (StyleFunc) Render

func (fn StyleFunc) Render(strs ...string) string

Render applies the style function to the provided strings.

type StyleOption

type StyleOption interface {
	ApplyStyle(*Styles)
}

StyleOption configures Styles using the functional options pattern.

func WithAppFlagName

func WithAppFlagName(style TextStyle) StyleOption

WithAppFlagName sets the app-level flag name style.

func WithAppFlagUsage

func WithAppFlagUsage(style TextStyle) StyleOption

WithAppFlagUsage sets the app-level flag usage style.

func WithAppTitle

func WithAppTitle(style TextStyle) StyleOption

WithAppTitle sets the app title style.

func WithArgumentMarker

func WithArgumentMarker(style TextStyle) StyleOption

WithArgumentMarker sets the argument marker style.

func WithArgumentName

func WithArgumentName(style TextStyle) StyleOption

WithArgumentName sets the argument name style.

func WithChildDesc

func WithChildDesc(style TextStyle) StyleOption

WithChildDesc sets the child description style.

func WithChildName

func WithChildName(style TextStyle) StyleOption

WithChildName sets the child name style.

func WithCommandFlagName

func WithCommandFlagName(style TextStyle) StyleOption

WithCommandFlagName sets the command-level flag name style.

func WithCommandFlagUsage

func WithCommandFlagUsage(style TextStyle) StyleOption

WithCommandFlagUsage sets the command-level flag usage style.

func WithCommandTitle

func WithCommandTitle(style TextStyle) StyleOption

WithCommandTitle sets the command title style.

func WithExample

func WithExample(style TextStyle) StyleOption

WithExample sets the example style.

func WithSectionHeading

func WithSectionHeading(style TextStyle) StyleOption

WithSectionHeading sets the section heading style.

func WithStyleAppDescription

func WithStyleAppDescription(style TextStyle) StyleOption

WithStyleAppDescription sets the app description style.

func WithStyleFlagName

func WithStyleFlagName(style TextStyle) StyleOption

WithStyleFlagName sets the flag name style (default for both app and command flags).

func WithStyleFlagUsage

func WithStyleFlagUsage(style TextStyle) StyleOption

WithStyleFlagUsage sets the flag usage style (default for both app and command flags).

func WithUsage

func WithUsage(style TextStyle) StyleOption

WithUsage sets the usage string style.

type Styles

type Styles struct {
	// AppTitle styles the application title in help output.
	AppTitle TextStyle

	// AppDescription styles the application description in help output.
	AppDescription TextStyle

	// CommandTitle styles command names in help output.
	CommandTitle TextStyle

	// SectionHeading styles section headings (e.g., "FLAGS", "ARGUMENTS") in help output.
	SectionHeading TextStyle

	// Usage styles usage strings in help output.
	Usage TextStyle

	// FlagName styles flag names (e.g., "--project") in help output.
	// Used as the default for both global and local flag names.
	FlagName TextStyle

	// FlagUsage styles flag usage text in help output.
	// Used as the default for both global and local flag usage.
	FlagUsage TextStyle

	// AppFlagName styles app-level flag names (root command flags shown everywhere).
	// Falls back to FlagName when unset.
	AppFlagName TextStyle

	// AppFlagUsage styles app-level flag usage text.
	// Falls back to FlagUsage when unset.
	AppFlagUsage TextStyle

	// CommandFlagName styles command-level flag names.
	// Falls back to FlagName when unset.
	CommandFlagName TextStyle

	// CommandFlagUsage styles command-level flag usage text.
	// Falls back to FlagUsage when unset.
	CommandFlagUsage TextStyle

	// ArgumentName styles argument names in help output.
	ArgumentName TextStyle

	// ArgumentMarker styles argument markers (e.g., "<name>", "[name]") in help output.
	ArgumentMarker TextStyle

	// ChildName styles child command and group names in help output.
	// Used for both groups and commands in the GROUPS and COMMANDS sections.
	ChildName TextStyle

	// ChildDesc styles child command and group descriptions in help output.
	// Used for both groups and commands in the GROUPS and COMMANDS sections.
	ChildDesc TextStyle

	// Example styles example text in help output.
	Example TextStyle
}

Styles defines styling hooks for textual CLI output such as help screens. All fields are optional - unset styles produce plain text output. Styles are compatible with lipgloss and can use any TextStyle implementation.

Example:

app.Styles = clix.Styles{
	AppTitle:     lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("63")),
	CommandTitle: lipgloss.NewStyle().Bold(true),
	FlagName:     lipgloss.NewStyle().Foreground(lipgloss.Color("205")),
}

func (*Styles) SetAppDescription

func (s *Styles) SetAppDescription(style TextStyle) *Styles

SetAppDescription sets the app description style and returns the styles for method chaining.

func (*Styles) SetAppFlagName

func (s *Styles) SetAppFlagName(style TextStyle) *Styles

SetAppFlagName sets the app-level flag name style and returns the styles for method chaining.

func (*Styles) SetAppFlagUsage

func (s *Styles) SetAppFlagUsage(style TextStyle) *Styles

SetAppFlagUsage sets the app-level flag usage style and returns the styles for method chaining.

func (*Styles) SetAppTitle

func (s *Styles) SetAppTitle(style TextStyle) *Styles

SetAppTitle sets the app title style and returns the styles for method chaining.

func (*Styles) SetArgumentMarker

func (s *Styles) SetArgumentMarker(style TextStyle) *Styles

SetArgumentMarker sets the argument marker style and returns the styles for method chaining.

func (*Styles) SetArgumentName

func (s *Styles) SetArgumentName(style TextStyle) *Styles

SetArgumentName sets the argument name style and returns the styles for method chaining.

func (*Styles) SetChildDesc

func (s *Styles) SetChildDesc(style TextStyle) *Styles

SetChildDesc sets the child description style and returns the styles for method chaining.

func (*Styles) SetChildName

func (s *Styles) SetChildName(style TextStyle) *Styles

SetChildName sets the child name style and returns the styles for method chaining.

func (*Styles) SetCommandFlagName

func (s *Styles) SetCommandFlagName(style TextStyle) *Styles

SetCommandFlagName sets the command-level flag name style and returns the styles for method chaining.

func (*Styles) SetCommandFlagUsage

func (s *Styles) SetCommandFlagUsage(style TextStyle) *Styles

SetCommandFlagUsage sets the command-level flag usage style and returns the styles for method chaining.

func (*Styles) SetCommandTitle

func (s *Styles) SetCommandTitle(style TextStyle) *Styles

SetCommandTitle sets the command title style and returns the styles for method chaining.

func (*Styles) SetExample

func (s *Styles) SetExample(style TextStyle) *Styles

SetExample sets the example style and returns the styles for method chaining.

func (*Styles) SetFlagName

func (s *Styles) SetFlagName(style TextStyle) *Styles

SetFlagName sets the flag name style and returns the styles for method chaining.

func (*Styles) SetFlagUsage

func (s *Styles) SetFlagUsage(style TextStyle) *Styles

SetFlagUsage sets the flag usage style and returns the styles for method chaining.

func (*Styles) SetSectionHeading

func (s *Styles) SetSectionHeading(style TextStyle) *Styles

SetSectionHeading sets the section heading style and returns the styles for method chaining.

func (*Styles) SetUsage

func (s *Styles) SetUsage(style TextStyle) *Styles

SetUsage sets the usage string style and returns the styles for method chaining.

type TextPromptOption

type TextPromptOption func(*PromptConfig)

TextPromptOption implements PromptOption for basic text prompts. These options work with all prompters.

func (TextPromptOption) Apply

func (o TextPromptOption) Apply(cfg *PromptConfig)

type TextPrompter

type TextPrompter struct {
	// In is the reader for user input (typically os.Stdin).
	In io.Reader

	// Out is the writer for prompt output (typically os.Stdout).
	Out io.Writer
}

TextPrompter implements Prompter for basic text input only. This is the default prompter in core - it only handles text prompts. Advanced prompt options (Select, MultiSelect, Confirm) are rejected at runtime with clear error messages directing users to the prompt extension. TextPrompter is the default prompt implementation. It supports basic text input and confirm prompts. For advanced prompts (select, multi-select), use the prompt extension which provides TerminalPrompter.

Example:

app.Prompter = clix.TextPrompter{
	In:  os.Stdin,
	Out: os.Stdout,
}

func (TextPrompter) Prompt

func (p TextPrompter) Prompt(ctx context.Context, opts ...PromptOption) (string, error)

Prompt displays a text prompt and reads the user's response. Accepts both struct-based PromptRequest and functional options for flexibility. Advanced prompt options (Select, MultiSelect) are rejected - use the prompt extension for those.

type TextStyle

type TextStyle interface {
	Render(...string) string
}

TextStyle describes an optional styling function applied to CLI text output. This interface is compatible with github.com/charmbracelet/lipgloss.Style, allowing lipgloss styles to be used directly without wrapping.

type Value

type Value interface {
	Set(string) error
	String() string
}

Value mirrors flag.Value but adds helpers for boolean flags.

Jump to

Keyboard shortcuts

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