{"id":44894491,"url":"https://github.com/sckelemen/clix","last_synced_at":"2026-02-17T20:00:50.957Z","repository":{"id":325954114,"uuid":"1085000120","full_name":"SCKelemen/clix","owner":"SCKelemen","description":"An opinionated, batteries-optional cli library for Go. Build beautiful, consistent, user-friendly cli applications easily.","archived":false,"fork":false,"pushed_at":"2025-12-12T14:14:25.000Z","size":74061,"stargazers_count":6,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-13T16:00:07.566Z","etag":null,"topics":["cli","go"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/SCKelemen/clix","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SCKelemen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-28T13:03:52.000Z","updated_at":"2025-12-12T14:14:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SCKelemen/clix","commit_stats":null,"previous_names":["sckelemen/clix"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/SCKelemen/clix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SCKelemen%2Fclix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SCKelemen%2Fclix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SCKelemen%2Fclix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SCKelemen%2Fclix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SCKelemen","download_url":"https://codeload.github.com/SCKelemen/clix/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SCKelemen%2Fclix/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29556404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T18:16:07.221Z","status":"ssl_error","status_checked_at":"2026-02-17T18:16:04.782Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","go"],"created_at":"2026-02-17T20:00:28.732Z","updated_at":"2026-02-17T20:00:50.949Z","avatar_url":"https://github.com/SCKelemen.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clix\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/SCKelemen/clix.svg)](https://pkg.go.dev/github.com/SCKelemen/clix)\n\n```\n  ██████╗ ██╗      ██╗ ██╗  ██╗\n ██╔════╝ ██║      ██║ ╚██╗██╔╝\n ██║      ██║      ██║  ╚███╔╝\n ██║      ██║      ██║  ██╔██╗\n ╚██████╗ ███████╗ ██║ ██╔╝ ██╗\n  ╚═════╝ ╚══════╝ ╚═╝ ╚═╝  ╚═╝\n```\n\n## Introduction\n\n`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](https://pkg.go.dev/github.com/SCKelemen/clix), which always reflects the latest published API surface.\n\n### Inspired by\n\n`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:\n\n- [spf13/cobra](https://github.com/spf13/cobra) – the battle-tested, code-generation friendly CLI toolkit\n- [peterbourgon/ff](https://github.com/peterbourgon/ff) – fast flag parsing with cohesive env/config loading\n- [manifoldco/promptui](https://github.com/manifoldco/promptui) – elegant interactive prompts for Go CLIs\n\n`clix` is designed to be **simple by default, powerful when needed**—starting with core functionality and allowing optional features through an extension system.\n\n## Principles\n\n`clix` follows a few core behavioral principles that ensure consistent and intuitive CLI interactions:\n\n1. **Groups show help**: Commands with children but no Run handler (groups) display their help surface when invoked, showing available groups and commands.\n\n2. **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.\n\n3. **Actionable commands prompt**: Commands without children that require arguments will automatically prompt for missing required arguments, providing a smooth interactive experience.\n\n4. **Help flags take precedence**: Global and command-level `-h`/`--help` flags always show help information, even if arguments are missing.\n\n5. **Configuration precedence**: Values are resolved in the following order (highest precedence first): Command flags \u003e App flags \u003e Environment variables \u003e Config file \u003e Defaults\n   - Command-level flag values (flags defined on the specific command)\n   - App-level flag values (flags defined on the root command, accessible via `app.Flags()`)\n   - Environment variables matching the flag's `EnvVar` or the default pattern `APP_KEY`\n   - Entries in `~/.config/\u003capp\u003e/config.yaml`\n   - Flag defaults defined on the command or app flag set\n\n## Goals\n\n- **Minimal overhead for simple apps**: Core library is lightweight, with optional features via extensions\n- **Declarative API**: Describe your CLI structure clearly and concisely\n- **Consistent behavior**: Predictable help, prompting, and command execution patterns\n- **Great developer experience**: Clear types, helpful defaults, and comprehensive examples\n- **Batteries-included when needed**: Extensions provide powerful features without polluting core functionality\n- **Structured output support**: Built-in JSON/YAML/text formatting for machine-readable output\n- **Interactive by default**: Smart prompting that guides users through required inputs\n\n## Not Goals\n\n- **Everything as a dependency**: Features like help commands, autocomplete, and version checking are opt-in via extensions\n- **Complex state management**: `clix` focuses on CLI structure, not application state\n- **Built-in templating or rich output**: While styling is supported, `clix` doesn't include markdown rendering or complex UI components by default\n\n## When to Use clix\n\n`clix` is a good fit if you want:\n\n- **A strict tree of groups and commands** – Clear hierarchy where groups organize commands and commands execute handlers\n- **Built-in prompting and config precedence** – Automatic prompting for missing arguments and consistent flag/env/config/default resolution\n- **Extensions instead of code generation** – Optional features via extensions rather than codegen tools\n- **Declarative API** – Describe your CLI structure clearly with structs, functional options, or builder-style APIs\n\nIf you need code generation, complex plugin systems, or a more flexible command model, consider [Cobra](https://github.com/spf13/cobra) or [ff](https://github.com/peterbourgon/ff).\n- **Command auto-generation**: You explicitly define your command tree\n- **Automatic flag inference**: Flags must be explicitly declared (though defaults, env vars, and config files reduce boilerplate)\n\n## Quick Start\n\nApplications built with `clix` work best when the executable wiring and the command implementations live in separate packages. A minimal layout looks like:\n\n```\ndemo/\n  cmd/demo/main.go\n  cmd/demo/app.go\n  internal/greet/command.go\n```\n\n`cmd/demo/main.go` bootstraps cancellation, logging, and error handling for the process:\n\n```go\n// cmd/demo/main.go\npackage main\n\nimport (\n        \"context\"\n        \"fmt\"\n        \"os\"\n)\n\nfunc main() {\n        app := newApp()\n\n        if err := app.Run(context.Background(), nil); err != nil {\n                fmt.Fprintln(app.Err, err)\n                os.Exit(1)\n        }\n}\n```\n\n`cmd/demo/app.go` owns the `clix.App` and root command definition while delegating child commands to the `internal/` tree:\n\n```go\n// cmd/demo/app.go\npackage main\n\nimport (\n        \"clix\"\n        \"clix/ext/help\"\n        \"example.com/demo/internal/greet\"\n)\n\nfunc newApp() *clix.App {\n        app := clix.NewApp(\"demo\")\n        app.Description = \"Demonstrates the clix CLI framework\"\n\n        var project string\n        app.Flags().StringVar(clix.StringVarOptions{\n                FlagOptions: clix.FlagOptions{\n                        Name:   \"project\",\n                        Usage:  \"Project to operate on\",\n                        EnvVar: \"DEMO_PROJECT\",\n                },\n                Value:   \u0026project,\n                Default: \"sample-project\",\n        })\n\n        root := clix.NewCommand(\"demo\")\n        root.Short = \"Root of the demo application\"\n        root.Run = func(ctx *clix.Context) error {\n                return clix.HelpRenderer{App: ctx.App, Command: ctx.Command}.Render(ctx.App.Out)\n        }\n        root.Children = []*clix.Command{\n                greet.NewCommand(\u0026project),\n        }\n\n        app.Root = root\n\n        // Add optional extensions\n        app.AddExtension(help.Extension{})\n\n        return app\n}\n```\n\nThe implementation of the `greet` command (including flags, arguments, and handlers) lives in `internal/greet`:\n\n```go\n// internal/greet/command.go\npackage greet\n\nimport (\n        \"fmt\"\n\n        \"clix\"\n)\n\nfunc NewCommand(project *string) *clix.Command {\n        cmd := clix.NewCommand(\"greet\")\n        cmd.Short = \"Print a friendly greeting\"\n\n        var name string\n        cmd.Flags.StringVar(clix.StringVarOptions{\n                FlagOptions: clix.FlagOptions{\n                        Name:       \"name\",\n                        Usage:      \"Name of the person to greet\",\n                        Required:   true,\n                        Prompt:     \"Name of the person to greet\",\n                        Positional: true,\n                },\n                Value: \u0026name,\n        })\n\n        cmd.PreRun = func(ctx *clix.Context) error {\n                fmt.Fprintf(ctx.App.Out, \"Using project %s\\n\", *project)\n                return nil\n        }\n        cmd.Run = func(ctx *clix.Context) error {\n                fmt.Fprintf(ctx.App.Out, \"Hello %s!\\n\", name)\n                return nil\n        }\n        cmd.PostRun = func(ctx *clix.Context) error {\n                fmt.Fprintln(ctx.App.Out, \"Done!\")\n                return nil\n        }\n        return cmd\n}\n```\n\nWhen no arguments are provided, `clix` will prompt the user for any required values. For example `demo greet` will prompt for the `name` flag before executing the command handler. You can also pass it positionally (`demo greet Alice`) or as a named flag (`demo greet --name Alice`). Because the root command's `Run` handler renders the help surface, invoking `demo` on its own prints the full set of available commands.\n\nThe full runnable version of this example (including additional flags and configuration usage) can be found in [`examples/basic`](examples/basic).\n\n## Features\n\n### Hierarchical Commands\n\nCommands can contain nested children (groups or commands), forming a tree structure. `clix` distinguishes between:\n\n- **Groups**: Commands with children but no Run handler (interior nodes that show help)\n- **Commands**: Commands with Run handlers (executable, may have children or be leaf nodes)\n\nEach command supports:\n- **Aliases**: Alternative names for the same command\n- **Usage metadata**: Short descriptions, long descriptions, examples\n- **Visibility controls**: Hidden commands for internal or experimental features\n- **Execution hooks**: `PreRun`, `Run`, and `PostRun` handlers\n\n**Creating groups and commands:**\n\n```go\n// Create a group (organizes child commands, shows help when called)\nusers := clix.NewGroup(\"users\", \"Manage user accounts\",\n        clix.NewCommand(\"create\"), // child command\n        clix.NewCommand(\"list\"),   // child command\n)\n\n// Or create a command with both handler and children\nauth := clix.NewCommand(\"auth\")\nauth.Short = \"Authentication commands\"\nauth.Run = func(ctx *clix.Context) error {\n        fmt.Println(\"Auth handler executed!\")\n        return nil\n}\nauth.AddCommand(clix.NewCommand(\"login\"))\nauth.AddCommand(clix.NewCommand(\"logout\"))\n\n// Groups show help, commands with handlers execute\n// cli users        -\u003e shows help\n// cli auth         -\u003e executes auth handler\n// cli auth login   -\u003e executes login child\n```\n\n### Flags and Configuration\n\nGlobal and command-level flags support:\n- **Environment variable defaults**: Automatically read from environment\n- **Config file defaults**: Persistent configuration in `~/.config/\u003capp\u003e/config.yaml`\n- **Flag variants**: Long (`--flag`), short (`-f`), with equals (`--flag=value`) or space (`--flag value`)\n- **Type support**: String, bool, int, int64, float64\n- **Custom validation**: Optional `Validate` function runs after parsing to reject invalid values\n- **Precedence**: Command flags \u003e App flags \u003e Environment variables \u003e Config file \u003e Defaults\n\n```go\nvar project string\napp.Flags().StringVar(clix.StringVarOptions{\n        FlagOptions: clix.FlagOptions{\n                Name:   \"project\",\n                Short:  \"p\",\n                Usage:  \"Project to operate on\",\n                EnvVar: \"MYAPP_PROJECT\",\n        },\n        Value:   \u0026project,\n        Default: \"default-project\",\n})\n```\n\n#### Typed configuration access (optional schema)\n\nWhen you read persisted configuration directly, you can use typed helpers:\n\n```go\nif retries, ok := app.Config.Int(\"project.retries\"); ok {\n        fmt.Fprintf(app.Out, \"Retry count: %d\\n\", retries)\n}\nif enabled, ok := app.Config.Bool(\"feature.enabled\"); ok \u0026\u0026 enabled {\n        fmt.Fprintln(app.Out, \"Feature flag is on\")\n}\n```\n\nIf you want `cli config set` to enforce types, register an optional schema:\n\n```go\napp.Config.RegisterSchema(\n        clix.ConfigSchema{\n                Key:  \"project.retries\",\n                Type: clix.ConfigInt,\n                Validate: func(value string) error {\n                        if value == \"0\" {\n                                return fmt.Errorf(\"retries must be positive\")\n                        }\n                        return nil\n                },\n        },\n        clix.ConfigSchema{\n                Key:  \"feature.enabled\",\n                Type: clix.ConfigBool,\n        },\n)\n```\n\nWith 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.\n\n### Positional Arguments\n\nFlags marked `Positional: true` accept values by position in addition to by name.\nBoth forms work: `cmd \u003cvalue\u003e` and `cmd --flag \u003cvalue\u003e`. Positional flags are assigned\nleft-to-right in registration order; named flags always take precedence. Boolean flags\ncannot be positional.\n\n- **Dual syntax**: Use positional or named form interchangeably\n- **Automatic prompting**: Missing required positional flags trigger interactive prompts\n- **Default values**: Optional positional flags can have defaults\n- **Smart labels**: Prompt labels default to title-cased flag names (e.g., `project-id` → `Project id`)\n- **Registration order**: First `Positional: true` flag = position 0, second = position 1, etc.\n- **Named precedence**: If `--flag value` is passed, the flag is skipped during positional assignment\n- **Validation**: Custom validation functions run before execution\n\n```go\nvar repository string\ncmd.Flags.StringVar(clix.StringVarOptions{\n        FlagOptions: clix.FlagOptions{\n                Name:       \"repository\",\n                Usage:      \"Repository to clone (OWNER/REPO)\",\n                Required:   true,\n                Prompt:     \"OWNER/REPO\",\n                Positional: true,\n                Validate: func(s string) error {\n                        if !strings.Contains(s, \"/\") {\n                                return fmt.Errorf(\"repository must be in OWNER/REPO format\")\n                        }\n                        return nil\n                },\n        },\n        Value: \u0026repository,\n})\n\n// All of these work:\n// gh repo clone sckelemen/clix\n// gh repo clone --repository sckelemen/clix\n```\n\n### Interactive Prompting\n\n`clix` provides several prompt types:\n- **Text input**: Standard text prompts with validation\n- **Select**: Navigable single-selection lists (requires prompt extension)\n- **Multi-select**: Multiple selection lists (requires prompt extension)\n- **Confirm**: Yes/no confirmation prompts\n\nPrompts automatically use raw terminal mode when available (for arrow key navigation) and fall back to line-based input otherwise.\n\nThe 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.\n\n**Struct-based API (recommended):**\n```go\n// Basic text prompt\nresult, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{\n        Label:   \"Enter name\",\n        Default: \"unknown\",\n})\n\n// Select prompt\nresult, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{\n        Label: \"Choose an option\",\n        Options: []clix.SelectOption{\n                {Label: \"Option A\", Value: \"a\"},\n                {Label: \"Option B\", Value: \"b\"},\n        },\n})\n\n// Confirm prompt\nresult, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{\n        Label:   \"Continue?\",\n        Confirm: true,\n})\n```\n\n**Functional options API:**\n```go\n// Basic text prompt\nresult, err := ctx.App.Prompter.Prompt(ctx,\n        clix.WithLabel(\"Enter name\"),\n        clix.WithDefault(\"unknown\"),\n)\n\n// Select prompt (requires prompt extension)\nimport \"clix/ext/prompt\"\nresult, err := ctx.App.Prompter.Prompt(ctx,\n        clix.WithLabel(\"Choose an option\"),\n        prompt.Select([]clix.SelectOption{\n                {Label: \"Option A\", Value: \"a\"},\n                {Label: \"Option B\", Value: \"b\"},\n        }),\n)\n\n// Confirm prompt\nresult, err := ctx.App.Prompter.Prompt(ctx,\n        clix.WithLabel(\"Continue?\"),\n        clix.WithConfirm(),\n)\n```\n\nBoth APIs can be mixed - functional options can be combined with `PromptRequest` structs, with later options overriding earlier values.\n\n### Structured Output\n\nGlobal `--format` flag supports `json`, `yaml`, and `text` output formats:\n\n```go\n// In your command handler\nreturn ctx.App.FormatOutput(data) // Uses --format flag automatically\n```\n\nCommands like `version` and `config list` automatically support structured output for machine-readable workflows.\n\n### Styling\n\nOptional styling hooks allow integration with packages like [`lipgloss`](https://github.com/charmbracelet/lipgloss):\n\n```go\nimport \"github.com/charmbracelet/lipgloss\"\n\nstyle := lipgloss.NewStyle().Foreground(lipgloss.Color(\"205\"))\napp.DefaultTheme.PrefixStyle = clix.StyleFunc(style.Render)\napp.Styles.SectionHeading = clix.StyleFunc(style.Render)\n// Style app-level vs. command-level flags differently if desired\napp.Styles.AppFlagName = clix.StyleFunc(style.Render)\napp.Styles.CommandFlagName = clix.StyleFunc(style.Render)\n```\n\nKey style hooks:\n\n- `AppFlagName` / `AppFlagUsage` – app-level flags defined on `app.Flags()`\n- `CommandFlagName` / `CommandFlagUsage` – command-level flags defined on `cmd.Flags()`\n- `FlagName` / `FlagUsage` – base styles applied when the more specific hooks are unset\n- `ChildName` / `ChildDesc` – section entries under **GROUPS** and **COMMANDS**\n\nStyling is optional—applications without styling still work perfectly.\n\n### Command Context\n\nCommand handlers receive a `*clix.Context` that embeds `context.Context` and provides:\n- Access to the active command\n- Application instance and configuration\n- Hydrated flag/config values via type-specific getters: `String()`, `Bool()`, `Int()`, `Int64()`, `Float64()`\n- Standard output/error streams\n- Standard context.Context functionality (cancellation, deadlines, values)\n\nAll getter methods follow the same precedence: **command flags \u003e app flags \u003e env \u003e config \u003e defaults**\n\n**Context Layering:**\n\n- `App.Run(ctx context.Context, ...)` accepts a standard `context.Context` for process-level cancellation and deadlines\n- For each command execution, clix builds a `*clix.Context` that embeds the original `context.Context` and adds CLI-specific data\n- Within handlers, pass `*clix.Context` directly to functions that accept `context.Context` (like `Prompter.Prompt`) - no need to use `ctx.Context`\n\n```go\ncmd.Run = func(ctx *clix.Context) error {\n        // Access CLI-specific data via type-specific getters\n        // All methods follow the same precedence: command flags \u003e app flags \u003e env \u003e config \u003e defaults\n        if project, ok := ctx.String(\"project\"); ok {\n                fmt.Fprintf(ctx.App.Out, \"Using project %s\\n\", project)\n        }\n        \n        if verbose, ok := ctx.Bool(\"verbose\"); ok \u0026\u0026 verbose {\n                fmt.Fprintf(ctx.App.Out, \"Verbose mode enabled\\n\")\n        }\n\n        if port, ok := ctx.Int(\"port\"); ok {\n                fmt.Fprintf(ctx.App.Out, \"Port: %d\\n\", port)\n        }\n\n        if timeout, ok := ctx.Int64(\"timeout\"); ok {\n                fmt.Fprintf(ctx.App.Out, \"Timeout: %d\\n\", timeout)\n        }\n        \n        if ratio, ok := ctx.Float64(\"ratio\"); ok {\n                fmt.Fprintf(ctx.App.Out, \"Ratio: %.2f\\n\", ratio)\n        }\n\n        // Use context.Context functionality (cancellation, deadlines)\n        select {\n        case \u003c-ctx.Done():\n                return ctx.Err()\n        default:\n        }\n\n        // Pass ctx directly to Prompter (it embeds context.Context)\n        value, err := ctx.App.Prompter.Prompt(ctx, clix.PromptRequest{\n                Label: \"Enter value\",\n        })\n        if err != nil {\n                return err\n        }\n\n        return nil\n}\n```\n\n## Extensions\n\nExtensions provide optional \"batteries-included\" features that can be added to your CLI application without adding overhead for simple apps that don't need them.\n\nThis design is inspired by [goldmark's extension system](https://github.com/yuin/goldmark), which allows features to be added without polluting the core library.\n\n### Philosophy\n\n**Simple by default, powerful when needed.** `clix` starts with minimal overhead:\n- Core command/flag/argument parsing\n- Flag-based help (`-h`, `--help`) - always available\n- Prompting UI\n- Configuration management (API only, not commands)\n\nEverything else is opt-in via extensions, including:\n- Command-based help (`cli help`)\n- Config management commands (`cli config`)\n- Shell completion (`cli autocomplete`)\n- Version information (`cli version`)\n- And future extensions...\n\n### Using Extensions\n\nAdd extensions to your app before calling `Run()`:\n\n```go\nimport (\n        \"clix\"\n        \"clix/ext/autocomplete\"\n        \"clix/ext/config\"\n        \"clix/ext/help\"\n        \"clix/ext/version\"\n)\n\napp := clix.NewApp(\"myapp\")\napp.Root = clix.NewCommand(\"myapp\")\n\n// Add extensions for optional features\napp.AddExtension(help.Extension{})         // Adds: myapp help [command]\napp.AddExtension(config.Extension{})       // Adds: myapp config, myapp config list, etc.\napp.AddExtension(autocomplete.Extension{}) // Adds: myapp autocomplete [shell]\napp.AddExtension(version.Extension{        // Adds: myapp version\n        Version: \"1.0.0\",\n        Commit:  \"abc123\",  // optional\n        Date:    \"2024-01-01\", // optional\n})\n\n// Flag-based help works without extensions: myapp -h, myapp --help\napp.Run(context.Background(), nil)\n```\n\n### Available Extensions\n\n#### Help Extension (`clix/ext/help`)\n\nAdds command-based help similar to man pages:\n- `cli help` - Show help for the root command\n- `cli help [command]` - Show help for a specific command\n\n**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.\n\n#### Config Extension (`clix/ext/config`)\n\nAdds configuration management commands using dot-separated key paths (e.g. `project.default`):\n- `cli config` - Show help for config commands (group)\n- `cli config list` - List persisted configuration as YAML (respects `--format=json|yaml|text`)\n- `cli config get \u003ckey_path\u003e` - Print the persisted value at `key_path`\n- `cli config set \u003ckey_path\u003e \u003cvalue\u003e` - Persist a new value\n- `cli config unset \u003ckey_path\u003e` - Remove the persisted value (no-op if missing)\n- `cli config reset` - Remove all persisted configuration from disk (flags/env/defaults still apply)\n\nOptional schemas can enforce types when setting values:\n\n```go\napp.Config.RegisterSchema(clix.ConfigSchema{\n        Key:  \"project.retries\",\n        Type: clix.ConfigInt,\n})\n```\n\nWith a schema, `config set project.retries 5` succeeds while `config set project.retries nope` fails with a helpful error.\n\n#### Autocomplete Extension (`clix/ext/autocomplete`)\n\nAdds shell completion script generation:\n- `cli autocomplete [bash|zsh|fish]` - Generate completion script for the specified shell\n- If no shell is provided, shows help\n\n#### Version Extension (`clix/ext/version`)\n\nAdds version information:\n- `cli version` - Show version information, including Go version and build info (supports `--format=json|yaml|text`)\n- Global `--version`/`-v` flag - Show simple version string (e.g., `cli --version` shows \"cli version 1.0.0\")\n\n```go\napp.AddExtension(version.Extension{\n        Version: \"1.0.0\",\n        Commit:  \"abc123\",  // optional\n        Date:    \"2024-01-01\", // optional\n})\n```\n\n**Zero overhead if not imported:** Extensions only add commands when imported and registered. Simple apps that don't import them pay zero cost.\n\n### Creating Extensions\n\nExtensions implement the `clix.Extension` interface:\n\n```go\npackage myextension\n\nimport \"github.com/SCKelemen/clix\"\n\ntype Extension struct {\n        // Optional: extension-specific configuration\n}\n\nfunc (e Extension) Extend(app *clix.App) error {\n        // Add commands, modify behavior, etc.\n        if app.Root != nil {\n                app.Root.AddCommand(MyCustomCommand(app))\n        }\n        return nil\n}\n```\n\nExtensions are applied lazily when `Run()` is called, or can be applied early with `ApplyExtensions()`.\n\nFor more details, see [`ext/README.md`](ext/README.md).\n\n## Key API Types\n\n### `clix.App`\n\nThe `App` struct represents a runnable CLI application and wires together the root command, global flag set, configuration manager, and prompting behavior.\n\n```go\ntype App struct {\n        Name        string\n        Version     string\n        Description string\n\n        Root        *Command\n        Config      *ConfigManager\n        Prompter    Prompter\n        Out         io.Writer\n        Err         io.Writer\n        In          io.Reader\n        EnvPrefix   string\n\n        DefaultTheme  PromptTheme\n        Styles        Styles\n}\n```\n\nKey methods:\n- `NewApp(name string) *App` - Construct a new application\n- `Run(ctx context.Context, args []string) error` - Execute the application\n- `AddExtension(ext Extension)` - Register an extension\n- `ApplyExtensions() error` - Apply all registered extensions\n- `OutputFormat() string` - Get the current output format (json/yaml/text)\n- `FormatOutput(data interface{}) error` - Format data using the current format\n\n### `clix.Command`\n\nA `Command` represents a CLI command. Commands can contain nested children (groups or commands), flags, argument definitions, and execution hooks.\n\nA Command can be one of three types:\n- **Group**: has children but no Run handler (interior node, shows help when called)\n- **Leaf Command**: has a Run handler but no children (executable leaf node)\n- **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)\n\n```go\ntype Command struct {\n        Name        string\n        Aliases     []string\n        Short       string\n        Long        string\n        Usage       string\n        Example     string\n        Hidden      bool\n        Flags       *FlagSet\n        Children    []*Command // Children of this command (groups or commands)\n\n        Run     Handler\n        PreRun  Hook\n        PostRun Hook\n}\n```\n\nKey methods:\n- `NewCommand(name string) *Command` - Construct a new executable command\n- `NewGroup(name, short string, children ...*Command) *Command` - Construct a group (interior node)\n- `AddCommand(cmd *Command)` - Register a child command or group\n- `IsGroup() bool` - Returns true if command is a group (has children, no Run handler)\n- `IsLeaf() bool` - Returns true if command is executable (has Run handler)\n- `Groups() []*Command` - Returns only child groups\n- `Commands() []*Command` - Returns only executable child commands\n- `VisibleChildren() []*Command` - Returns all visible child commands and groups\n- `Path() string` - Get the full command path from root\n\n### `clix.PromptRequest`\n\nA `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.\n\n```go\ntype PromptRequest struct {\n        Label    string\n        Default  string\n        Validate func(string) error\n        Theme    PromptTheme\n\n        // Options for select-style prompts\n        Options []SelectOption\n\n        // MultiSelect enables multi-selection mode\n        MultiSelect bool\n\n        // Confirm is for yes/no confirmation prompts\n        Confirm bool\n\n        // ContinueText for multi-select prompts\n        ContinueText string\n}\n```\n\n**Functional Options API:**\n\nFor convenience, `clix` also provides functional options that can be used instead of or alongside `PromptRequest`:\n\n**Core options (available in `clix` package):**\n- `WithLabel(label string)` - Set the prompt label\n- `WithDefault(def string)` - Set the default value\n- `WithValidate(validate func(string) error)` - Set validation function\n- `WithTheme(theme PromptTheme)` - Set the prompt theme\n- `WithConfirm()` - Enable yes/no confirmation prompt\n- `WithCommandHandler(handler PromptCommandHandler)` - Register handler for special key commands\n- `WithKeyMap(keyMap PromptKeyMap)` - Configure keyboard shortcuts and bindings\n- `WithNoDefaultPlaceholder(text string)` - Set placeholder text when no default exists\n\n**Advanced options (require `clix/ext/prompt` extension):**\n- `prompt.Select(options []SelectOption)` - Create a select prompt\n- `prompt.MultiSelect(options []SelectOption)` - Create a multi-select prompt\n- `prompt.WithContinueText(text string)` - Set continue button text for multi-select\n- `prompt.Confirm()` - Alias for `WithConfirm()` (for convenience)\n\nBoth APIs can be mixed. For example:\n```go\n// Mix struct with functional options\nresult, err := prompter.Prompt(ctx,\n        clix.PromptRequest{Label: \"Name\"},\n        clix.WithDefault(\"unknown\"),\n)\n```\n\n### `clix.Extension`\n\nThe `Extension` interface allows optional features to be added to an application.\n\n```go\ntype Extension interface {\n        Extend(app *App) error\n}\n```\n\n## Examples\n\n- [`examples/basic`](examples/basic): End-to-end application demonstrating commands, flags, prompting, and configuration usage.\n- [`examples/gh`](examples/gh): A GitHub CLI-style hierarchy with familiar groups, commands, aliases, and interactive prompts.\n- [`examples/gcloud`](examples/gcloud): A Google Cloud CLI-inspired tree with large command groups, global flags, and configuration interactions.\n- [`examples/lipgloss`](examples/lipgloss): Demonstrates prompt and help styling using [`lipgloss`](https://github.com/charmbracelet/lipgloss), including select, multi-select, and confirm prompts.\n- [`examples/multicli`](examples/multicli): Demonstrates sharing command implementations across multiple CLI applications with different hierarchies, similar to Google Cloud's gcloud/bq pattern.\n\n## Contributing\n\nWe welcome issues and pull requests. To keep the review cycle short:\n\n1. Fork the repo and create a feature branch.\n2. Format Go sources with `gofmt` (or `go fmt ./...`).\n3. Run `go test ./...` to ensure tests and examples stay green.\n4. Include tests and docs for new behavior.\n\nIf you plan a larger change, feel free to open an issue first so we can discuss the approach.\n\n## Release Process\n\nWe 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:\n\n1. Ensure `main` is green: `gofmt ./... \u0026\u0026 go test ./...`.\n2. Update any docs or examples that mention the version (e.g., changelog snippets if applicable).\n3. Tag the release using Go-style semver and push the tag:\n   ```bash\n   git tag -a v1.0.0 -m \"clix v1.0.0\"\n   git push origin v1.0.0\n   ```\n4. 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.\n\nFollowing this flow keeps the module path (`github.com/SCKelemen/clix`) stable and gives downstream consumers reproducible builds.\n\n## Developers \u0026 Maintainers\n\n- [Marcos Quesada Samaniego](https://github.com/marcosQuesada)\n- [Samuel Kelemen](https://github.com/SCKelemen)\n\n## Receiving Dependabot PRs When clix Releases\n\nWhenever 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:\n\n1. **Import clix via the module path**:\n   ```go\n   import \"github.com/SCKelemen/clix\"\n   ```\n2. **Pin a version in your `go.mod`**:\n   ```go\n   require github.com/SCKelemen/clix v1.0.0\n   ```\n   Dependabot will bump this line when a newer semver-compatible version exists.\n3. **Add `.github/dependabot.yml` with a Go entry**:\n   ```yaml\n   version: 2\n   updates:\n     - package-ecosystem: \"gomod\"\n       directory: \"/\"          # path containing go.mod\n       schedule:\n         interval: \"weekly\"    # or daily/monthly\n   ```\n\nThat’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.\n\n## License\n\n`clix` is available under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsckelemen%2Fclix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsckelemen%2Fclix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsckelemen%2Fclix/lists"}